Wer auf dem Apache-Webserver Webanwendungen (CGI-Programme; meistens Perl, Python oder PHP) laufen lässt und über diverse Apache-Direktiven wie SetEnvIf oder RewriteRule Umgebungsvariablen gesetzt hatte, wird oft erstaunt feststellen, dass diese gesetzten Umgebungsvariablen gar nicht einlesbar sind.
Anwendungsfall für Umgebungsvariablen in CGI
Es könnte über die Apache-Direktive SetEnvIf eine Variable gesetzt werden, wenn bestimmte Umstände vorliegen wie die Beispiele zeigen.
Mein Anwendungsfall wäre der Hinweis für ein CGI auf bestimme Mobilbrowser wie das unvollständige Beispiel erläutern soll.
Apache-Konfiguration
Die Apache-Konfiguration dazu wäre:
### Apache config MOBILEDETECT ###
SetEnvIfNoCase User-Agent "Mozilla/5.0 (Linux; U; Android 2.2.1; fr-ch; A43 Build/FROYO) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1" is_mobile_browser=1
SetEnvIfNoCase User-Agent "Mozilla/5.0 (Danger hiptop 3.4; U; AvantGo 3.2)" is_mobile_browser=1
SetEnvIfNoCase User-Agent "Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A543a Safari/419.3" is_mobile_browser=1
SetEnvIfNoCase User-Agent "Nokia6230i/2.0 (03.25) Profile/MIDP-2.0 Configuration/CLDC-1.1" is_mobile_browser=1
SetEnvIfNoCase User-Agent "SonyEricssonW800i/R1BC Java/SEMC-Java/2.0 Profile/MIDP-2.0 Configuration/CLDC-1.1" is_mobile_browser=1
SetEnvIfNoCase User-Agent "Blackberry" is_mobile_browser=1
SetEnvIfNoCase User-Agent "Opera Mini" is_mobile_browser=1
SetEnvIfNoCase User-Agent "iPad"
SetEnvIfNoCase User-Agent "DoCoMo/2.0 SH901iC(c100;TB;W24H12)" is_mobile_browser=1
SetEnvIfNoCase User-Agent "J-PHONE/5.0/V801SA/SN123456789012345 SA/0001JP Profile/MIDP-1.0" is_mobile_browser=1
SetEnvIfNoCase User-Agent "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; PPC; 240x320)" is_mobile_browser=1
SetEnvIfNoCase User-Agent"OPWV-SDK UP.Browser/7.0.2.3.119 (GUI) MMP/2.0 Push/PO" is_mobile_browser=1
SetEnvIfNoCase User-Agent "Mozilla/5.0 (PDA; NF35WMPRO/1.0; like Gecko) NetFront/3.5" is_mobile_browser=1
SetEnvIfNoCase User-Agent "Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; PPC; 240x320; HP iPAQ h6300)"
SetEnvIfNoCase User-Agent "SonyEricssonG700/R100 Mozilla/4.0 (compatible; MSIE 6.0; Symbian OS; 958) Opera 8.65 [ru]" is_mobile_browser=1
Inzwischen könnten noch weitere Umleitungen und Änderungen in den Request vorkommen. Ein CGI-Programm in Perl sollte dann später auswerten, ob ein Mobilbrowser angefragt hat und darauf reagiere.
Der Apache hat die Möglichkeit bestimmte Aktionen mit der Direktive Action zu definieren und dafür ein CGI aufzurufen. Das CGI wird dabei als Handler verwendet:
Action mymobile /cgi-bin/mobiredir.pl
<Files ~ "\.html?">
SetHandler mymobile
</Files>
Damit übernimmt das CGI mobiredir.pl sämtliche URI, die an die Dateiendungen .htm oder .html gehen.
Perl-CGI als Action-Handler
Da ja durch die SetEnvIf-Direkten schon früher die Umgebungsvariable is_mobile_browser gesetzt wurde, falls ein Mobilbrowser verwendet wurde, sollte jetzt folgender Perl-Code als eine Art Umleitung auf die Mobil-Webseite möglich sein:
#!/usr/bin/perl -w
use strict;
use CGI qw(:cgi);
my $DEBUG = defined param('DEBUG');
my $url = url(-full=>1, -query=>1, -path_info=>1);
my $host = virtual_host() || remote_host();
my $cgi = script_name();
my $real_url = $url;
$real_url =~ s/$cgi//;
my $new_host = $host;
$new_host =~ s/(www\.)?/mobil\./; # falls eine www.-Domain vorliegt
my $mobil_url = $url;
$mobil_url =~ s/$host/$new_host/;
$mobil_url =~ s/$cgi//;
if ($DEBUG) {
print header(-type=>'text/plain');
print <<TXT;
called CGI: $cgi
internal URI: $url
Host: $host
New Host: $new_host
URI: $real_url
Mobil-URI: $mobil_url
TXT
}
elsif ($ENV{'is_mobile_browser'}) {
print redirect($mobil_url);
}
else {
# gib die Normale Seite aus
}
1;
Problem
Leider kommt es aber nie zu einer Weiterleitung. Weil nämlich das CGI gar nicht die Umgebungsvariable is_mobile_browser erhält.
Wie kommt das?
Nichtverfügbarkeit der gesetzten Umgebungsvariablen
Grund für die Nichteinlesbarkeit ist oft eine spezielle Betriebsart der CGI auf dem Webserver. Diese werden meistens aus Sicherheitsgründen der Nutzertrennung mit dem Programm suexec gestartet.
suexec hat die spezielle Eigenschaft, dass es nur bestimmte Umgebungsvariablen weiterreicht. Es ist in der Apachedokumentation zu suexec nicht klar zu erkennen, welche das sind und sie benennt nur folgendes im Abschnitt suEXEC Security Model:
Can we successfully clean the process environment to ensure safe operations?
suEXEC cleans the process' environment by establishing a safe execution PATH (defined during configuration), as well as only passing through those variables whose names are listed in the safe environment list (also created during configuration).
suexec lässt demnach nur besondere Variablen zu. Welche das sind, ist wieder woanders, in der Dokumentation Environment Variables in Apache versteckt:
Some Caveats
(...)
- When suexec is used to launch CGI scripts, the environment will be cleaned down to a set of safe variables before CGI scripts are launched. The list of safe variables is defined at compile-time in suexec.c.
- For portability reasons, the names of environment variables may contain only letters, numbers, and the underscore character. In addition, the first character may not be a number. Characters which do not match this restriction will be replaced by an underscore when passed to CGI scripts and SSI pages.
Die Nomenklatur für die Namen wäre ja klar, aber welche sind erlaubt? Erst der genaue Blick in den Quellcode für suexec.c zeigt, welche. Der relevante Abschnitt:
94 static const char *const safe_env_lst[] =
95 {
96 /* variable name starts with */
97 "HTTP_",
98 "SSL_",
99
100 /* variable name is */
101 "AUTH_TYPE=",
102 "CONTENT_LENGTH=",
103 "CONTENT_TYPE=",
104 "DATE_GMT=",
105 "DATE_LOCAL=",
106 "DOCUMENT_NAME=",
107 "DOCUMENT_PATH_INFO=",
108 "DOCUMENT_ROOT=",
109 "DOCUMENT_URI=",
110 "GATEWAY_INTERFACE=",
111 "HTTPS=",
112 "LAST_MODIFIED=",
113 "PATH_INFO=",
114 "PATH_TRANSLATED=",
115 "QUERY_STRING=",
116 "QUERY_STRING_UNESCAPED=",
117 "REMOTE_ADDR=",
118 "REMOTE_HOST=",
119 "REMOTE_IDENT=",
120 "REMOTE_PORT=",
121 "REMOTE_USER=",
122 "REDIRECT_HANDLER=",
123 "REDIRECT_QUERY_STRING=",
124 "REDIRECT_REMOTE_USER=",
125 "REDIRECT_STATUS=",
126 "REDIRECT_URL=",
127 "REQUEST_METHOD=",
128 "REQUEST_URI=",
129 "SCRIPT_FILENAME=",
130 "SCRIPT_NAME=",
131 "SCRIPT_URI=",
132 "SCRIPT_URL=",
133 "SERVER_ADMIN=",
134 "SERVER_NAME=",
135 "SERVER_ADDR=",
136 "SERVER_PORT=",
137 "SERVER_PROTOCOL=",
138 "SERVER_SIGNATURE=",
139 "SERVER_SOFTWARE=",
140 "UNIQUE_ID=",
141 "USER_NAME=",
142 "TZ=",
143 NULL
144 };
Das sieht ganz informativ aus!
Zeile 96–98 besagt also, dass die Variablennamen, die mit HTTP_ oder SSL_ beginnen, weitergereicht werden. Das bedeutet, dass auch so seltsame Variablennamen wie HTTP_blaaah_blubb oder HTTP_isn_browser das CGI erreichen können ohne durch suexec geschluckt zu werden.
Lösung
Anstatt in der früher genannten Apache-Konfiguration MOBILEDETECT die Umgebungsvariable is_mobile_browser zu verwenden, einfach HTTP_ davorsetzen; aus is_mobile_browser wird HTTP_is_mobile_browser.
Eine Beispielzeile aus der korrekten Konfiguration:
SetEnvIfNoCase User-Agent "Opera Mini" HTTP_is_mobile_browser=1
Damit das früher genannte Perl-CGI funktioniert, muss auch anstatt
else if ($ENV{'is_mobile_browser'}) {
die Zeile
else if ($ENV{'HTTP_is_mobile_browser'}) {
verwendet werden.
Weitere Hinweise
SetEnv und URL-Rewrites
Weiterhin erwähnt die Apache-Dokumentation zu Umgebungsvariablen einen Umstand, der bei SetEnv & Co. unbedingt zu berücksichtigen ist, und meines Erachtens eine kleine Falle birgt:
Some Caveats
(...)
The SetEnv directive runs late during request processing meaning that directives such as SetEnvIf and RewriteCond will not see the variables set with it.
Mit SetEnv gesetzte Variablen werden erst spät ins den Request eingefügt, sodass diese in den Direktiven SetEnvIf oder RewriteCond nicht verwendbar sind.
Andere Programmiersprachen
Die oben genannte Problematik gilt im Grunde genommen für alle CGI-Programme, die mit Wrappern wie suexec laufen. Im Zweifelsfall muss das getestet werden.
Es betrifft also auch PHP, Python oder andere Programme die als CGI laufen.
Module mod_perl, mod_php und FastCGI
Ich möchte noch darauf hinweisen, dass die früher genannten Probleme mit den Umgehungsvariablen bei der Verwendung von mod_perl, mod_php, mod_python oder FastCGI nicht entstehen. Auch das ist für das jeweilige Programm zu testen.
Allerdings sind diese Module das nicht auf allen Servern oder Webhosterpaketen möglich, auch aus Datenschutz- und Sicherheitsgründen.
Quellen
- http://httpd.apache.org/docs/2.2/suexec.html
- http://httpd.apache.org/docs/2.2/env.html
- http://svn.apache.org/viewvc/httpd/httpd/branches/2.2.x/support/suexec.c?view=markup
- http://httpd.apache.org/docs/2.2/mod/mod_setenvif.html#setenvif
- http://www.perl-community.de/bat/poard/thread/16657
Fazit
Mir ist schon klar, dass die oben genannte Umleitung auch ohne CGI druch das Modul mod_rewrite möglich ist; es sollte im Artikel nur klar gemacht werden, dass das Zusammenspiel von CGI und Umgebungsvariablen problematisch sein kann.
Wer weitere Tipps und Hinweise hat, kann die gerne hier im Kommentarbereich posten.