Posts mit dem Label Developer werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Developer werden angezeigt. Alle Posts anzeigen

09.09.2014

Signierten Code mit Java erstellen

Man hat's nicht leicht, mit den ganzen Updates Schritt zu halten ... Dauernd Sicherheitslücken in Windows, Flash, Java, sogar in Android findet sich die eine oder andere Lücke.
Um den Erfolg der Updates zu überprüfen, insbesondere für Flash und Java, hatte ich mir vor langer Zeit eine ganz primitive Webseite erstellt, die mir die aktuellen Versionsnummern der installierten Plugins anzeigen kann.

Seit Version 1.7 der Java Runtime werden aber lästige Warnungen ausgegeben, dass demnächst nur noch signierter Code ausgeführt wird, dass Code, der von http- und nicht https-Schema nachgeladen wird, als unsicher betrachtet wird, und überhaupt lassen die Sicherheitseinstellungen der JRE demnächst gar nix mehr zu.

Also hab ich mich mal drangesetzt und aus meiner .class-Datei eine .jar-Datei gemacht, die ich mit meinen eigenen, selbst erstellten Key signiert habe. Das ist aber natürlich nur die halbe Miete, weil man auch der JRE im Browser klarmachen muss, dass der self-signed Code auch wirklich zulässig ist. Man muss also in der policy-Datei auch noch Änderungen vornehmen. Hier ist der ganze Ablauf zusammengestellt, den ich durchlaufen musste, bis meine simple JRE-Versionsabfrage mit Firefox 31, Chrome 36 und JRE 1.8.0.11 wieder funktioniert hat.

Hier ist zunächst der Code für die Ausgabe der JRE-Versionsnummer:
import java.applet.Applet;
import java.awt.Color;
import java.awt.Label;
public class JavaVersionDisplayApplet extends Applet
{
  private Label V;
  public JavaVersionDisplayApplet() {
    this.setBackground(Color.pink);
    V = new Label(" Java Version: " + System.getProperty("java.version") + " from " + System.getProperty("java.vendor"));
    this.add(V);
  }
}
Um diesen Code zu kompilieren, muss das Java Development Kit mit dem javac-Compiler installiert sein. Da Java einigermaßen kompatibel zu alten Versionen ist, kann man auch mit einer aktuellen JRE wie 1.8 noch Code ausführen, der mit Version 1.4 kompiliert wurde. Eine Krankheit von Java ist, dass der Dateiname exakt dem Klassennamen in der Datei entsprechen muss, hier also "JavaVersionDisplayApplet.java".
javac JavaVersionDisplayApplet.java
Als nächstes müssen noch zwei Dinge vorbereitet werden: ein Schlüsselpaar für das Signieren und eine Manifest-Datei, in der der Java-Code etwas genauer beschrieben wird, also etwas poetischer gesagt: Meta-Daten.

Zunächst also das Schlüsselpaar mit dem Programm keytool erzeugen, dass bei der JRE und beim JDK mitgeliefert wird.
keytool -genkeypair -alias thomas -keystore seeling.jks -dname "CN=Thomas Seeling, OU=System Administration, O=Kleintierpraxis Berstadt, L=Berstadt, ST=Hessen, C=DE" -keypass Password1 -storepass Password2
Im weiteren verwende ich immer den Aliasnamen "thomas" für den Schlüssel. Je nach Zusammenhang ist damit entweder der private oder der öffentliche Schlüssel gemeint.

Als nächstes benötige ich noch den öffentlichen Schlüssel separat, damit ich ihn in meinem Browser importieren kann. Dieser Schritt würde entfallen, wenn ich den Schlüssel von einer öffentlichen Root CA signieren lasse oder wenn ich mir selbst eine Root CA gebaut habe und den öffentlichen Schlüssel dieser Root CA schon in meinem Browser hätte.
keytool -export -keystore seeling.jks -alias thomas -storepass Password2 -file tseeling.cer
Ich mag meine Schlüssel lieber in base64-kodiert, also wandle ich die binäre .cer-Datei noch in eine PEM-Datei um:
openssl x509 -in tseeling.cer -inform DER -out tseeling.pem -outform PEM
Weil dies "nur" ein öffentlicher Schlüssel ist, benötige ich nach dem Export aus dem Java-Keyring kein Passwort mehr.
Diese PEM-Datei kann ich nun bequem über ein Transportmittel wie Email, ftp etc. zu meinem Browser-PC übertragen und dort importieren.

Desweiteren benötige ich die schon erwähnte Manifest-Datei, die mit in die .jar-Datei gepackt und signiert werden muss. Diese Datei sieht bei mir so aus:
Permissions: sandbox
Codebase: https://admin.moeller-seeling.local/*
Application-Name: JavaVersion
Der "Application-Name" ist hier nur "Schmuck am Nachthemd" und wird für eine Infobox benötigt, in der die JRE dem Benutzer anzeigt, wer da gerade ausgeführt werden soll.

Wenn das alles zusammengestellt ist, wird nun in zwei Schritten aus der kompilierten Java-Datei und dem Manifest zunächst eine .jar-Datei erzeugt und dann diese Datei mit dem zuvor erstellten privaten Schlüssel signiert.
jar -cfm JavaVersion.jar JavaVersion.txt JavaVersionDisplayApplet.class
jarsigner -keystore /opt/jdk/jre/lib/security/seeling.jks -keypass Password1 -storepass Password2 -verbose JavaVersion.jar thomas 

 Puh, fast geschafft! Jetzt noch diese .jar-Datei in einem kleinen HTML benutzen und vom Webserver ausliefern lassen. Darauf gehe ich nur ganz kurz ein, hier ist ein Beispiel für den HTML-Code:
<table border="1">
<tr><th> The version and vendor from the JRE</th>
<td align="center">
<applet height="60" alt="Browser has Java disabled"
hspace="22" width="440"
archive="JavaVersion.jar"
code="JavaVersionDisplayApplet.class">
</applet>
</td></tr>
</table>
Soweit, so gut. Das war der erste Schritt auf dem Webserver.

Auf dem Browser-PC sind auch noch kleine Schritte nötig, damit man ein self-signed .jar ausführen darf:
man muss in die Security-Policy einen Eintrag machen, dass signierte Dateien von bestimmten Programmierern erlaubt sind, und man muss den öffentlichen Schlüssel dieses Programmierers in den Standard-Keystore der JRE importieren, die der Browser im Java-Plugin verwendet, oder alternativ einen anderen Keystore angeben, in dem dieses Zertifikat enthalten ist.

Auf Windows findet sich das unter "%ProgramFiles(x86)%\java\jre8\lib\security\java.policy" (für die Java-Version 1.8).

Zu dieser (i.a. schon vorhandenen Datei) im Klartextformat habe ich folgende Einträge hinzugeführt:

keystore "file:${java.home}/lib/security/seeling.jks", "jks";
grant signedBy "thomas" {
  permission java.security.AllPermission, signedBy "thomas";
};
Zu der Liste der vertrauenswürdigen Zertifikate habe ich meinen öffentlichen Schlüssel hinzugefügt, den ich weiter oben als PEM-Format gespeichert hatte.
keytool -importcert -noprompt -trustcacerts -alias thomas -file tseeling.pem -keystore "%ProgramFiles(x86)%\java\jre8\lib\security\cacerts" -storepass changeit
Das Passwort der Schlüsseldatei "cacerts" ist üblicherweise "changeit". Natürlich ändert es nie jemand ;)

So, nachdem also nun ganz viele kleine Gemeinheiten geschafft sind, müsste im Browser ein Applet funktionieren, das die JRE-Versionsnummer ausgibt. Wenn man auch nur einen dieser Schritte weglässt, klappt es nicht und entweder der Browser oder die JRE beschimpfen mich, dass alles ganz schröcklich unsicher ist.

21.11.2013

So baut man ein aktuelles Komplettpaket Acrobat Reader X

Hilfsvariablen setzen


set D=C:\tmp\AR10
set S=C:\tmp\download
set MSI=AdbeRdr1000_de_DE.msi
set AR=ftp://ftp.adobe.com/pub/adobe/reader/win/10.x

Ordner erstellen

if exist %S%\nul rmdir /S /Q %S%
if exist %D%\nul rmdir /S /Q %D%
mkdir %S%
mkdir %D%
cd /D %S%

wget beschaffen

Bei sourceforge.net herunterladen und das ZIP auspacken. wget ist ein cooles Kommandozeilenwerkzeug zum Abrufen von URLs, sei es http, https oder ftp.

Dateien herunterladen

wget -nv -nc -N %AR%/10.0.0/de_DE/%MSI%
wget -nv -nc -N %AR%/10.1.8/misc/AdbeRdrUpd1018.msp

Patches in das Grundpaket integrieren

msiexec /a %S%\%MSI% /qb- TARGETDIR=%D%
msiexec /p %S%\AdbeRdrUpd1018.msp /a %D%\%MSI% /qb- TARGETDIR=%D%
Die Angabe /a führt eine reine "Administrator"-Installation durch, d.h. die Software wird nicht auf dem PC installiert, auf dem der Befehl aufgerufen wird, sondern es wird nur vorbereitet, dass die Software auf anderen PCs installiert werden kann. Mit /p werden die heruntergeladenen Patches in die Basissoftware integriert. Die Reihenfolge von /p und /a im letzten Befehl oben muss man einhalten, sonst klappt es nicht.

Recht angenehm: man muss nur den neuesten Patch für den Acrobat Reader herunterladen und nicht sukzessiv alle. Derzeit aktuell ist 10.1.8.

Der Ordner C:\tmp\download wird danach nicht mehr benötigt und kann gelöscht werden. Den vollständigen Ordner C:\tmp\AR10 kopiert man nun auf ein Netzlaufwerk, z.B. ein NAS oder eine Festplatte am DSL-/LTE-Router.

Installation

msiexec /i I:\win\adobe\AR10\AdbeRdr1000_de_DE.msi /qb-!
Natürlich muss man hier das Beispiel I:\win\adobe\AR10 entsprechend an die eigenen Verhältnisse anpassen.

Credits: das Prinzip und die Downloadlinks hab ich mir hier im heise-Forum abgeguckt.

15.09.2013

Portabler Apache-Webserver selbst gebaut

Heute hab ich mal wieder eine kleine Anleitung vorbereitet. Sozusagen aus aktuellem Anlass ;)

Ich habe mich vor kurzem bereit erklärt, als Aushilfe beim Förderverein der Wölfersheimer Schulen einzusteigen und den Webserver ein wenig zu betreuen, weil der edle Spender, der das aufgebaut und bisher betreut hat, beruflich viel um die Ohren hat.

Es geht aber natürlich nicht an, dass jemand direkt am produktiven System Änderungen durchführt, es muss also eine Testumgebung her, in der ich Dinge ausprobieren und auch sonst "herumspielen" kann. Idealerweise sollte es möglich sein, die Änderungen an den Vorstand des Vereins zu schicken, dort begutachten zu lassen, und dann die freigegebenen Änderungen auf den "Live"-Webserver zu überspielen.

Normalerweise ist es kein großes Problem, ein paar Webseiten zusammenzubauen und den Kollegen die Änderungen als Email zuzuschicken.

Das scheitert aber in dem Moment, wenn der Webserver aktive Inhalte ausliefert, wie z.B. Datenbankabfragen durchführt oder einfach nur aktive Seiten in Form von Python-Skripten oder JSP ausliefert. Soweit geht es zum Glück beim Förderverein nicht, es handelt sich "nur" um PHP-Skripte, die vom Apache ausgeführt werden und in HTML-Seiten resultieren, die beim Browser aufschlagen.

Also besteht die Testumgebung minimal aus einem Apache Webserver und dem PHP-Skriptinterpreter. Zusammen mit ein bißchen weiterem Kleinkram besteht der "PortableApache" aus knapp 40 MB Material. Das passt bequem auf den allerkleinsten USB-Stick, in ein Eckchen auf dem Smartphone oder MP3-Player zum bequemen Herumtragen.

Was braucht man nun? Das hängt vom Betriebssystem ab. Für Windows (weil die Vorstandskollegen Windows einsetzen) benötigt man die Installationsdateien von Apache httpd und ein PHP-Paket. Ein paar Bemerkungen zu den Windows-Paketen: es gibt verschiedene vorgefertigte Pakete, unter den Kategorien "VC9", "VC10", "VC11". Das sind Grundpakete von Microsoft (die sogenannte C-Runtime). Die Versionen 9 und 10 laufen mit Windows XP, Version 11 setzt mindestens Windows 7 voraus. Deshalb habe ich hier die Variante mit VC9 verlinkt.

Jetzt werden aus den ZIP-Dateien ein paar Dateien ausgepackt, man braucht gar nicht so viel ;)

Im Internet findet man viele Anleitungen, wie man einen portablen Apache Webserver zusammenbaut. Allen gemeinsam ist, dass sie die Verzeichnisstruktur für Apache beibehalten, es gibt also Verzeichnisse für Logfiles, Module, Konfigurationsdateien usw. Braucht man auf einem USB-Stick eigentlich nicht. Ich habe deshalb alles zusammengeworfen und ein Verzeichnis für Apache und ein Verzeichnis für PHP festgelegt. Das reicht ;)

Man beginnt mit einem Verzeichnis "PortableApache" und legt dort Unterverzeichnisse für "apache" und "php" an. Außerdem benötigt man noch ein Unterverzeichnis, in dem die Dokumente liegen, die ausgeliefert werden sollen. Im Beispiel unten ist dies das Verzeichnis "html", das parallel zu "apache" und "php" liegt. In der Konfigurationsdatei wird es dann als "../html" angesprochen. Auf diese Weise sind die Dokumente unabhängig vom Standort des Apache immer erreichbar.

In das Verzeichnis "apache" gehört nun folgendes:
  1. mod_mime.so
  2. mod_cgi.so
  3. openssl.cfg.cnf
  4. msvcr100.dll
  5. openssl.exe
  6. pcre.dll
  7. ssleay32.dll
  8. zlib1.dll
  9. htpasswd.exe
  10. httpd.exe
  11. libapr-1.dll
  12. libapriconv-1.dll
  13. libaprutil-1.dll
  14. libeay32.dll
  15. libhttpd.dll
  16. libxml2.dll
  17. httpd-ssl.conf
  18. magic
  19. mime.types
  20. httpd.conf
  21. mod_mime_magic.so
  22. mod_rewrite.so
  23. mod_setenvif.so
  24. mod_ssl.so
  25. mod_status.so
  26. mod_substitute.so
  27. mod_userdir.so
  28. mod_version.so
  29. mod_vhost_alias.so
  30. mod_alias.so
  31. mod_authn_core.so
  32. mod_authz_core.so
  33. mod_deflate.so
  34. mod_dir.so
  35. mod_env.so
  36. mod_headers.so
  37. mod_imagemap.so
  38. mod_include.so
  39. mod_info.so
  40. mod_log_config.so
  41. mod_log_debug.so
  42. mod_log_forensic.so
  43. mod_logio.so
  44. php5apache2_4.dll
Die Datei php5apache2_4.dll nimmt man aus dem PHP-Paket.

Im Verzeichnis "php" braucht man ein bißchen mehr Kram, je nachdem, welche  PHP-Funktionen den Skripten zur Verfügung stehen sollen. Wer sich mit PHP auskennt, kann hier natürlich brutal ausmisten, für Grundbedürfnisse kann man meinen Vorschlag noch deutlich verschlanken.

  1. glib-2.dll
  2. gmodule-2.dll
  3. icudt49.dll
  4. icuin49.dll
  5. icuio49.dll
  6. icule49.dll
  7. iculx49.dll
  8. icutest49.dll
  9. icutu49.dll
  10. icuuc49.dll
  11. libeay32.dll
  12. libenchant.dll
  13. libenchant_ispell.dll
  14. libenchant_myspell.dll
  15. libpq.dll
  16. libsasl.dll
  17. php-cgi.exe
  18. php-win.exe
  19. php.exe
  20. php.gif
  21. php.ini-development
  22. php.ini-production
  23. php5apache2_4.dll
  24. php5nsapi.dll
  25. php5ts.dll
  26. ssleay32.dll
Das Unterverzeichnis "php/ext" enthält folgende Dateien:
  1. php_bz2.dll
  2. php_curl.dll
  3. php_enchant.dll
  4. php_exif.dll
  5. php_fileinfo.dll
  6. php_gd2.dll
  7. php_gettext.dll
  8. php_gmp.dll
  9. php_intl.dll
  10. php_mbstring.dll
  11. php_openssl.dll
  12. php_shmop.dll
  13. php_sockets.dll
Um sicherzustellen, dass PHP funktioniert, sollte man nun an dieser Stelle eine Kommandozeile öffnen (Eingabeaufforderung, cmd.exe), in das Verzeichnis "PortableApache/php" wechseln und testen, ob der PHP-Interpreter sich meldet und seine Versionsnummer bekannt gibt:
H:\PortableApache\php>.\php -v
PHP 5.4.19 (cli) (built: Aug 21 2013 01:12:03)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.4.0, Copyright (c) 1998-2013 Zend Technologies


Für diese veränderte Umgebung  muss man dann natürlich auf die Konfigurationsdateien beträchtlich anpassen, damit das Apache-httpd-Programm weiß, wo alle weiteren Module und Dateien zu finden sind.

Man braucht also eine httpd.conf, in der die Verzeichnisse alle aufgeführt sind. Wichtig ist, dass in dieser Konfigurationsdatei keine absoluten Verzeichnisnamen enthalten sind, sondern nur solche, die relativ zum Arbeitsverzeichnis des Webservers (des Prozesses httpd.exe) formuliert sind. Zur Verdeutlichung habe ich diese Stellen unten fett markiert.
Vom Standpunkt der Sicherheit aus gesehen ist diese Webserver-Datei natürlich absolut unvollständig, da fehlen noch einige Anweisungen, um Einbrüche von außen zu verhindern. Das ist aber nicht der Zweck eines "PortableApache". Diese Konfigurationsdatei da unten also bitte nicht als Vorlage für einen "echten" Webserver im Internet verwenden!

Listen 80
LoadModule alias_module mod_alias.so
LoadModule logio_module mod_logio.so
LoadModule log_config_module mod_log_config.so
LoadModule dir_module mod_dir.so
LoadModule mime_module mod_mime.so
LoadModule authn_core_module mod_authn_core.so
LoadModule authz_core_module mod_authz_core.so
LoadModule cgi_module mod_cgi.so
LoadModule env_module mod_env.so
LoadModule rewrite_module mod_rewrite.so
LoadModule setenvif_module mod_setenvif.so
LoadModule ssl_module mod_ssl.so
LoadModule php5_module php5apache2_4.dll
ServerName localhost:80
DocumentRoot "../html"
ServerAdmin you@yourdomain.de
# Include httpd-ssl.conf

ScriptAlias /cgi-bin/ "../cgi-bin/"
LogFormat "%h %l %u %t \"%r\" %>s %b" common
CustomLog access.log common
ErrorLog  error.log
PidFile   httpd.pid

<IfModule php5_module>
  AddHandler application/x-httpd-php .php
  PHPIniDir "../php"
</IfModule>

<IfModule rewrite_module>
  RewriteEngine On
</IfModule>

<IfModule php5_module>
  DirectoryIndex index.php index.htm index.html
</IfModule>

<IfModule dir_module>
  DirectoryIndex index.html index.htm
</IfModule>

<IfModule mime_module>
  TypesConfig "mime.types"
  AddType application/x-compress .Z
  AddType application/x-gzip .gz .tgz
  AddType application/x-bzip2 .bz2 .tz2
</IfModule>

<IfModule ssl_module>
  SSLRandomSeed startup builtin
  SSLRandomSeed connect builtin
</IfModule>

<Directory "../html">
  AllowOverride All
</Directory>


Das Apache-Modul für "rewrite" brauche ich, damit ich solche Spielereien mit der Datei ".htaccess" in den Datenverzeichnissen machen kann:

RewriteEngine on
Options +FollowSymlinks
RewriteBase /

RewriteRule (.*)_(.*)_(.*).html$ /_start.php?cat=$1&id=$2&edit=$3
RewriteRule (.*)_(.*).html$ /_start.php?cat=$1&id=$2
RewriteRule (.*).html$ /_start.php?cat=$1


Ich habe da oben auch schon den Anfang einer SSL-Konfiguration angedeutet, aber natürlich funktioniert das nur, wenn man sich mit dem openssl-Programm ein eigenes Zertifikat baut, also einen private key. Dazu später vielleicht mal einen eigenen Artikel, wie man sich selbst zur root-CA-Instanz macht und dann für sich selbst Keys erzeugt und signiert.

Für eine erste Prüfung, ob diese httpd.conf auch wirklich funktioniert, ruft man "httpd.exe -t" auf (-t testet, ob eine Konfigurationsdatei einigermaßen funktionsfähig aussieht). Wenn man hier die Meldung "Syntax OK" erhält, kann man fast schon loslegen ;)

Für die bequeme Nutzung ist noch ein kleines Startskript vonnöten, das den Apache aufruft. Ich habe es "start.cmd" genannt und direkt in das Verzeichnis "PortableApache" gesteckt.

cd /D "%~dp0apache"
PATH %~dp0php;%PATH%
.\httpd.exe -f httpd.conf



Der Inhalt dieses kleinen Webservers wird nun angezeigt, wenn man mit einem Browser die URL "http://localhost" oder "http://127.0.0.1" aufruft.

19.11.2012

Eclipse und Android - was man alles falsch machen kann

Heute mal was für Programmierer und solche, die es werden wollen ...
Wahrscheinlich amüsieren sich die Profis über meine ersten Stolperschritte mit einer neuen Eclipse-Installation. Ich hab's ja nicht so mit GUIs, ich bin ein reiner Kommandozeilenmensch ;)

Es war einmal ... ein Eclipse Indigo mit Android SDK auf einem WXP-System zur Entwicklung einer kleinen App für Android 2.2.
Android 2.2 als Target, weil sich exakt mit dieser Version das API für den Zugriff auf eine SD-Karte eingeführt wurde (getExternalFilesDir()).
Dann war lange Zeit Pause, weil die App einfach so fröhlich vor sich hin funktionierte.
Nach einem Jahr und etwas mehr war ich genervt von den neuen Android-Versionen, die an mir vorübergingen, weil Samsung keine Updates für die Galaxy-S-Geräte auf die Reihe bekam. Also Anschaffung Nexus7 und Galaxy Nexus ;), Verkauf Archos 101 (Android 2.2.1), Galaxy i9000 (2.3.3). Am Rand: Nexus7 seit 2 Tagen auf 4.2, GNexus noch auf 4.1.2.

Interessanterweise funktioniert die bisherige App für den Wetterauer Tiernotdienstkalender (aus dem Play Store) auch auf diesen Nexus-Geräten, die ja prinzipiell keine SD-Karten unterstützen (außer mit Tricks auf gerooteten Geräten und einem USB-OTG-Adapter ...).

Irgendwann kam ein neuer PC mit Linux (RHEL 6.3) und Eclipse Juno und die Lust, ein bißchen weiter an den bisherigen Apps zu basteln und auch mal am lang geplanten und siechenden Romanverwaltungsprogramm (für PR und die anderen Serien, die ich sammle) weiterzuschrauben.

Also:
  1. Eclipse Juno installiert
  2. Android SDK installiert
  3. Android plugin für Eclipse installiert
  4. Android SDK Manager zum Download diverser Google Images und APIs gestartet
  5. An diversen Permissions geschraubt, bis Eclipse, AVD und ADT als User (nicht root) funktionieren
  6.  Sourcen der Apps in den Eclipse-Workspace kopiert
  7. Freundliche Fehlermeldungen über R.java
  8. Clean Project
  9. R.java wird in gen/ nicht neu erzeugt ;(
  10. Fehlersuche bei stackoverflow.com und diversen anderen
  11. Alle Tipps der Reihe nach ausprobieren ...
  12. z.b. "manage imports" erzeugt immer wieder "import android.R;" (Erklärung dafür: das Android SDK stößt auf einen Fehler und bricht ab. R.java wird nicht erzeugt, deshalb bastelt Eclipse sich irgendwas zurecht ;( )
  13. z.b. "clean"
  14. z.b. "properties->android tools->fix project properties"
  15. z.b. "Build Path->Order"
  16. z.b. "per-project java build environment"
  17. Aus lauter Verzweiflung dann statt dem IBM JRE 1.6.0.11 noch Sun JDK 1.7.09 installiert und eclipse -vm /opt/jdk_1.7.0_09/jre/bin aufgerufen.
  18. z.b. Prüfen aller XML auf Korrektheit
  19. Im grafischen Editor für das UI fiel mir auf, dass nix angezeigt wurde, und zum ersten Mal wenigstens mit einem Hinweis: keine Version angegeben für Emulator!
  20. Also Preferences->Android aufgerufen
  21. Dort wurde nur Version 4.0.3 und 4.1.2 angezeigt, obwohl ich in der Zwischenzeit auch die benötigte 2.2 mit dem SDK Manager geladen hatte.
  22. Meine App hat aber minSdkVersion="8", also brauche ich Google API 8 zum Bauen.
  23. Android SDK Pfad neu gesetzt und Eclipse neu gestartet.
  24. Jetzt werden alle im SDK Manager geladenen Android-Versionen korrekt angezeigt. Bingo!
  25. Normalerweise kenne ich den "Neustarten"-Trick ja nur von Windows ;)
  26. Und die Moral von der Geschicht': man soll nicht Sachen parallel machen, hier Nachladen von Android-APIs mit dem SDK Manager und schon mit dem Entwickeln in Eclipse anfangen.
Des weiteren habe ich meinen Code etwas umgestellt, um sowohl mit Geräten ohne als auch solchen mit SD-Karte umzugehen.

Alt:
        public static void setDataDir(Context c) {
                if (okStorage()) {
                        DataDir = c.getExternalFilesDir(null).toString();
                        logger("setdatadir.ext", DataDir);
                } else {
                        logger("setdatadir.error", "no external storage");
                }
        }

        public static boolean okStorage() {
                StorageState = Environment.getExternalStorageState();
                if (Environment.MEDIA_MOUNTED.equals(StorageState)) {
                        StorageAvailable = StorageWriteable = true;
                } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(StorageState)) {
                        StorageAvailable = true;
                        StorageWriteable = false;
                } else {
                        StorageAvailable = StorageWriteable = false;
                }
                return StorageWriteable;
        }


Neu:
        public static void setDataDir(Context c) {
                if (okExtStorage()) {
                        DataDir = c.getExternalFilesDir(null).toString();
                        logger("setdatadir.ext", DataDir);
                } else {
                        DataDir = c.getFilesDir().toString();
                        StorageAvailable = StorageWriteable = true;
                        logger("setdatadir.int", DataDir);
                }
        }

        public static boolean okStorage() {
                return (DataDir != null);
        }

        public static boolean okExtStorage() {
                StorageState = Environment.getExternalStorageState();
                if (Environment.MEDIA_MOUNTED.equals(StorageState)) {
                        StorageAvailable = StorageWriteable = true;
                } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(StorageState)) {
                        StorageAvailable = true;
                        StorageWriteable = false;
                } else {
                        StorageAvailable = StorageWriteable = false;
                }
                return StorageWriteable;
        }