allow_url_include has been bashed lately for being "not good enough",
and there is a kernel of truth to that, though where the ultimate blame
falls if of course a touchy subject.
So rather than continue the fight over who's shoulders the job of
security should fall on, how about the attached patch which puts a
little more power in the hands of the user/site-admin to control what
can be treated as a url include, and how it can be treated that way.
The short version of the attached patch is this:
allow_url_fopen and allow_url_include continue to accept boolean flags
in order to behave just as they do now: true/on allows anything,
false/off allows only those wrappers without the is_url bit set.
In addition, they can be set to a colon delimited list of wrapper names
(e.g. allow_url_include=file:compress.bz2:compress.zlib ) When used
in this manner, the value of a wrapper's is_url flag is meaningless,
only the name is considered. If it's in this list, it's allowed,
otherwise not.
I've also applied the same concept as was recently added to
open_basedir, that is; These settings can be tightened during
runtime/perdir context (but not loosened). For example, if the
system-wide setting for allow_url_fopen is on, and a given script wants
to restrict itself to just local files, they can:
ini_set("allow_url_fopen", "file");
Comments welcome, flames not.
-Sara
Note: I changed the internal names for these settings in order to force
3rd party extensions to review their code if they rely on inspecting
those values.
Index: main/php_globals.h
===================================================================
RCS file: /repository/php-src/main/php_globals.h,v
retrieving revision 1.109
diff -u -p -r1.109 php_globals.h
--- main/php_globals.h 1 Jan 2007 09:29:35 -0000 1.109
+++ main/php_globals.h 17 Jan 2007 00:55:27 -0000
@@ -124,7 +124,8 @@ struct _php_core_globals {
zend_bool modules_activated;
zend_bool file_uploads;
zend_bool during_request_startup;
- zend_bool allow_url_fopen;
+ char *allow_url_fopen_list;
+ char *allow_url_include_list;
zend_bool always_populate_raw_post_data;
zend_bool report_zend_debug;
@@ -137,7 +138,6 @@ struct _php_core_globals {
char *disable_functions;
char *disable_classes;
- zend_bool allow_url_include;
#ifdef PHP_WIN32
zend_bool com_initialized;
#endif
Index: main/main.c
===================================================================
RCS file: /repository/php-src/main/main.c,v
retrieving revision 1.719
diff -u -p -r1.719 main.c
--- main/main.c 9 Jan 2007 18:38:38 -0000 1.719
+++ main/main.c 17 Jan 2007 00:55:27 -0000
@@ -420,8 +420,8 @@ PHP_INI_BEGIN()
PHP_INI_ENTRY("disable_functions", "",
PHP_INI_SYSTEM, NULL)
PHP_INI_ENTRY("disable_classes", "",
PHP_INI_SYSTEM, NULL)
- STD_PHP_INI_BOOLEAN("allow_url_fopen", "1",
PHP_INI_SYSTEM, OnUpdateBool, allow_url_fopen,
php_core_globals, core_globals)
- STD_PHP_INI_BOOLEAN("allow_url_include", "0",
PHP_INI_SYSTEM, OnUpdateBool, allow_url_include,
php_core_globals, core_globals)
+ STD_PHP_INI_BOOLEAN("allow_url_fopen", "1",
PHP_INI_ALL, OnUpdateAllowUrl,
allow_url_fopen_list, php_core_globals, core_globals)
+ STD_PHP_INI_BOOLEAN("allow_url_include", "0",
PHP_INI_ALL, OnUpdateAllowUrl,
allow_url_include_list, php_core_globals, core_globals)
STD_PHP_INI_BOOLEAN("always_populate_raw_post_data", "0",
PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateBool,
always_populate_raw_post_data, php_core_globals,
core_globals)
STD_PHP_INI_ENTRY("realpath_cache_size", "16K", PHP_INI_SYSTEM,
OnUpdateLong, realpath_cache_size_limit, virtual_cwd_globals, cwd_globals)
STD_PHP_INI_ENTRY("realpath_cache_ttl", "120", PHP_INI_SYSTEM,
OnUpdateLong, realpath_cache_ttl, virtual_cwd_globals, cwd_globals)
@@ -1507,6 +1507,12 @@ static void core_globals_dtor(php_core_g
if (core_globals->disable_classes) {
free(core_globals->disable_classes);
}
+ if (core_globals->allow_url_fopen_list) {
+ free(core_globals->allow_url_fopen_list);
+ }
+ if (core_globals->allow_url_include_list) {
+ free(core_globals->allow_url_include_list);
+ }
}
/* }}} */
Index: main/php_streams.h
===================================================================
RCS file: /repository/php-src/main/php_streams.h,v
retrieving revision 1.123
diff -u -p -r1.123 php_streams.h
--- main/php_streams.h 16 Jan 2007 20:36:04 -0000 1.123
+++ main/php_streams.h 17 Jan 2007 00:55:27 -0000
@@ -21,6 +21,8 @@
#ifndef PHP_STREAMS_H
#define PHP_STREAMS_H
+#include "php_ini.h"
+
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
@@ -165,7 +167,7 @@ typedef struct _php_stream_wrapper_ops {
struct _php_stream_wrapper {
php_stream_wrapper_ops *wops; /* operations the wrapper can perform */
void *abstract; /* context for the
wrapper */
- int is_url; /* so that
PG(allow_url_fopen) can be respected */
+ int is_url; /* so that
PG(allow_url_fopen_list)/PG(allow_url_include_list) can be respected */
/* support for wrappers to return (multiple) error messages to the
stream opener */
int err_count;
@@ -658,6 +660,11 @@ PHPAPI void php_stream_wrapper_log_error
PHPAPI int _php_stream_make_seekable(php_stream *origstream, php_stream
**newstream, int flags STREAMS_DC TSRMLS_DC);
#define php_stream_make_seekable(origstream, newstream, flags)
_php_stream_make_seekable((origstream), (newstream), (flags) STREAMS_CC
TSRMLS_CC)
+PHP_INI_MH(OnUpdateAllowUrl);
+PHPAPI int php_stream_wrapper_is_allowed(const char *wrapper, int wrapper_len,
const char *setting TSRMLS_DC);
+#define php_stream_allow_url_fopen(wrapper, wrapper_len)
php_stream_wrapper_is_allowed((wrapper), (wrapper_len),
PG(allow_url_fopen_list) TSRMLS_CC)
+#define php_stream_allow_url_include(wrapper, wrapper_len)
php_stream_wrapper_is_allowed((wrapper), (wrapper_len),
PG(allow_url_include_list) TSRMLS_CC)
+
/* Give other modules access to the url_stream_wrappers_hash and
stream_filters_hash */
PHPAPI HashTable *_php_stream_get_url_stream_wrappers_hash(TSRMLS_D);
#define php_stream_get_url_stream_wrappers_hash()
_php_stream_get_url_stream_wrappers_hash(TSRMLS_C)
Index: main/streams/streams.c
===================================================================
RCS file: /repository/php-src/main/streams/streams.c,v
retrieving revision 1.146
diff -u -p -r1.146 streams.c
--- main/streams/streams.c 16 Jan 2007 20:36:04 -0000 1.146
+++ main/streams/streams.c 17 Jan 2007 00:55:27 -0000
@@ -2096,6 +2096,9 @@ PHPAPI php_stream_wrapper *php_stream_lo
}
/* TODO: curl based streams probably support file:// properly */
if (!protocol || !strncasecmp(protocol, "file", n)) {
+ /* fall back on regular file access */
+ php_stream_wrapper *plain_files_wrapper =
&php_plain_files_wrapper;
+
if (protocol) {
int localhost = 0;
@@ -2132,32 +2135,37 @@ PHPAPI php_stream_wrapper *php_stream_lo
return NULL;
}
+ /* The file:// wrapper may have been disabled/overridden */
if (FG(stream_wrappers)) {
- /* The file:// wrapper may have been
disabled/overridden */
-
- if (wrapperpp) {
- /* It was found so go ahead and provide it */
- return *wrapperpp;
- }
-
- /* Check again, the original check might have not known
the protocol name */
- if (zend_hash_find(wrapper_hash, "file",
sizeof("file"), (void**)&wrapperpp) == SUCCESS) {
- return *wrapperpp;
+ if (!wrapperpp || zend_hash_find(wrapper_hash, "file",
sizeof("file"), (void**)&wrapperpp) == FAILURE) {
+ if (options & REPORT_ERRORS) {
+ php_error_docref(NULL TSRMLS_CC,
E_WARNING, "Plainfiles wrapper disabled");
+ }
+ return NULL;
}
+ /* Handles overridden plain files wrapper */
+ plain_files_wrapper = *wrapperpp;
+ }
+
+ if (!php_stream_allow_url_fopen("file", sizeof("file") - 1) ||
+ ((options & STREAM_OPEN_FOR_INCLUDE) &&
!php_stream_allow_url_include("file", sizeof("file") - 1)) ) {
if (options & REPORT_ERRORS) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING,
"Plainfiles wrapper disabled");
+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
"file:// wrapper is disabled in the server configuration");
}
return NULL;
}
-
- /* fall back on regular file access */
- return &php_plain_files_wrapper;
+
+ return plain_files_wrapper;
}
- if ((wrapperpp && (*wrapperpp)->is_url) && (!PG(allow_url_fopen) ||
((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include))) ) {
+ if (!php_stream_allow_url_fopen(protocol, n) ||
+ ((options & STREAM_OPEN_FOR_INCLUDE) &&
!php_stream_allow_url_include(protocol, n)) ) {
if (options & REPORT_ERRORS) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING, "URL
file-access is disabled in the server configuration");
+ /* protocol[n] probably isn't '\0' */
+ char *protocol_dup = estrndup(protocol, n);
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s://
wrapper is disabled in the server configuration", protocol_dup);
+ efree(protocol_dup);
}
return NULL;
}
@@ -2866,6 +2874,230 @@ PHPAPI int _php_stream_path_decode(php_s
}
/* }}} */
+/* {{{ allow_url_fopen / allow_url_include Handlers */
+
+PHPAPI int php_stream_wrapper_is_allowed(const char *wrapper, int wrapper_len,
const char *setting TSRMLS_DC)
+{
+ int setting_len = setting ? strlen(setting) : 0;
+ const char *s = setting, *e = s + setting_len;
+
+ if (wrapper_len == (sizeof("zlib") - 1) && strncasecmp("zlib", wrapper,
sizeof("zlib") - 1) == 0) {
+ wrapper = "compress.zlib";
+ wrapper_len = sizeof("compress.zlib") - 1;
+ }
+
+ if (!setting || !setting_len) {
+ /* NULL or empty indicates that only is_url == 0 wrappers are
allowed */
+ HashTable *wrapper_hash = (FG(stream_wrappers) ?
FG(stream_wrappers) : &url_stream_wrappers_hash);
+ php_stream_wrapper **wrapperpp;
+ char *wrapper_dup;
+
+ if (wrapper_len == (sizeof("file") - 1) && strncasecmp("file",
wrapper, sizeof("file") - 1) == 0) {
+ /* file:// is non-url */
+ return 1;
+ }
+
+ wrapper_dup = estrndup(wrapper, wrapper_len);
+ php_strtolower(wrapper_dup, wrapper_len);
+ if (FAILURE == zend_hash_find(wrapper_hash, wrapper_dup,
wrapper_len + 1, (void**)&wrapperpp)) {
+ /* Wrapper not found */
+ efree(wrapper_dup);
+ return 0;
+ }
+
+ if ((*wrapperpp)->is_url) {
+ return 0;
+ }
+
+ /* Wrapper exists and is not is_url */
+ return 1;
+ }
+
+ if (setting_len == 1 && *setting == '*') {
+ /* "*" means everything is allowed */
+ return 1;
+ }
+
+ /* Otherwise, scan list */
+ while (s < e) {
+ const char *p = php_memnstr((char*)s, ":", 1, (char*)e);
+
+ if (!p) {
+ p = e;
+ }
+
+ if (wrapper_len == (p - s) &&
+ strncasecmp(s, wrapper, p - s) == 0) {
+ /* wrapper found in list */
+ return 1;
+ }
+
+ s = p + 1;
+ }
+
+ return 0;
+}
+
+/* allow_url_*_list accepts:
+ *
+ * 1/on to enable all URL prefixes
+ * 0/off to disable all is_url=1 wrappers
+ * A colon delimited list of wrappers to allow (wildcards allowed)
+ * e.g. file:gzip:compress.*:php
+ */
+PHP_INI_MH(OnUpdateAllowUrl)
+{
+#ifndef ZTS
+ char *base = (char *) mh_arg2;
+#else
+ char *base = (char *) ts_resource(*((int *) mh_arg2));
+#endif
+ char **allow = (char **) (base+(size_t) mh_arg1);
+
+ /* BC Enable */
+ if ((new_value_length == 1 && *new_value == '1') ||
+ (new_value_length == (sizeof("on") - 1) &&
strncasecmp(new_value, "on", sizeof("on") - 1) == 0) ) {
+
+ if (*allow && strcmp(*allow, "*") == 0) {
+ /* Turning on, but that's no change from current, so
leave it alone */
+ return SUCCESS;
+ }
+
+ if (stage != PHP_INI_STAGE_STARTUP) {
+ /* Not already on, and not in SYSTEM context, fail */
+ return FAILURE;
+ }
+
+ /* Otherwise, turn on setting */
+ if (*allow) {
+ free(*allow);
+ }
+
+ *allow = zend_strndup("*", 1);
+
+ return SUCCESS;
+ }
+
+ /* BC disable */
+ if ((new_value_length == 1 && *new_value == '0') ||
+ (new_value_length == (sizeof("off") - 1) &&
strncasecmp(new_value, "off", sizeof("off") - 1) == 0) ) {
+
+ /* Always permit shutting off allowurl settings */
+ if (*allow) {
+ free(*allow);
+ }
+ *allow = NULL;
+
+ return SUCCESS;
+ }
+
+ /* Specify as list */
+ if (stage == PHP_INI_STAGE_STARTUP) {
+ /* Always allow new settings in startup stage */
+ if (*allow) {
+ free(*allow);
+ }
+ *allow = zend_strndup(new_value, new_value_length);
+
+ return SUCCESS;
+ }
+
+ /* In PERDIR/RUNTIME context, do more work to ensure we're only
tightening the restriction */
+
+ if (*allow && strcmp(*allow, "*") == 0) {
+ /* Currently allowing everying, so whatever we set it to will
be more restrictive */
+ free(*allow);
+ *allow = zend_strndup(new_value, new_value_length);
+
+ return SUCCESS;
+ }
+
+ if (!*allow) {
+ /* Currently allowing anything with is_url == 0
+ * So long as this list doesn't contain any is_url == 1, allow
it
+ */
+ HashTable *wrapper_hash = (FG(stream_wrappers) ?
FG(stream_wrappers) : &url_stream_wrappers_hash);
+ char *s = new_value, *e = new_value + new_value_length;
+
+ while (s < e) {
+ php_stream_wrapper **wrapper;
+ char *p = php_memnstr(s, ":", 1, e);
+ char *scan;
+ int scan_len;
+
+ if (!p) {
+ p = e;
+ }
+
+ /* file:// is never a URL */
+ if ( (p - s) == (sizeof("file") - 1) && strncasecmp(s,
"file", sizeof("file") - 1) == 0 ) {
+ /* file is not a URL */
+ s = p + 1;
+ continue;
+ }
+
+ if ( (p - s) == (sizeof("zlib") - 1) && strncasecmp(s,
"zlib", sizeof("zlib") - 1) == 0 ) {
+ /* Wastful since we know that compress.zlib is
already lower cased, but forgivable */
+ scan = estrndup("compress.zlib",
sizeof("compress.zlib") - 1);
+ scan_len = sizeof("compress.zlib") - 1;
+ } else {
+ scan = estrndup(s, p - s);;
+ scan_len = p - s;
+ php_strtolower(scan, scan_len);
+ }
+
+ if (FAILURE == zend_hash_find(wrapper_hash, scan,
scan_len + 1, (void**) &wrapper)) {
+ /* Unknown wrapper, not allowed in this context
*/
+ efree(scan);
+ return FAILURE;
+ }
+ efree(scan);
+
+ if ((*wrapper)->is_url) {
+ /* Disallowed is_url wrapper specified when
trying to escape is_url == 0 context */
+ return FAILURE;
+ }
+
+ /* Seems alright so far... */
+ s = p+1;
+ }
+
+ /* All tests passed, allow it */
+ *allow = zend_strndup(new_value, new_value_length);
+
+ return SUCCESS;
+ }
+
+ /* The current allows are restricted to a specific list,
+ * Make certain that our new list is a subset of that list
+ */
+ {
+ char *s = new_value, *e = new_value + new_value_length;
+
+ while (s < e) {
+ char *p = php_memnstr(s, ":", 1, e);
+
+ if (!p) {
+ p = e;
+ }
+
+ if (!php_stream_wrapper_is_allowed(s, p - s, *allow
TSRMLS_CC)) {
+ /* Current settings don't allow this wrapper,
deny */
+ return FAILURE;
+ }
+
+ s = p + 1;
+ }
+
+ free(*allow);
+ *allow = zend_strndup(new_value, new_value_length);
+
+ return SUCCESS;
+ }
+}
+
+/* }}} */
+
/*
* Local variables:
* tab-width: 4
Index: ext/standard/php_fopen_wrapper.c
===================================================================
RCS file: /repository/php-src/ext/standard/php_fopen_wrapper.c,v
retrieving revision 1.58
diff -u -p -r1.58 php_fopen_wrapper.c
--- ext/standard/php_fopen_wrapper.c 1 Jan 2007 09:29:32 -0000 1.58
+++ ext/standard/php_fopen_wrapper.c 17 Jan 2007 00:55:27 -0000
@@ -187,7 +187,8 @@ php_stream * php_stream_url_wrap_php(php
}
if (!strcasecmp(path, "input")) {
- if ((options & STREAM_OPEN_FOR_INCLUDE) &&
!PG(allow_url_include) ) {
+ /* Override default behavior for php://input when used as an
include and allow_url_include is being used in BC (off) mode */
+ if ((options & STREAM_OPEN_FOR_INCLUDE) &&
!PG(allow_url_include_list) ) {
if (options & REPORT_ERRORS) {
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"URL file-access is disabled in the server configuration");
}
@@ -197,7 +198,8 @@ php_stream * php_stream_url_wrap_php(php
}
if (!strcasecmp(path, "stdin")) {
- if ((options & STREAM_OPEN_FOR_INCLUDE) &&
!PG(allow_url_include) ) {
+ /* Override default behavior for php://stdin when used as an
include and allow_url_include is being used in BC (off) mode */
+ if ((options & STREAM_OPEN_FOR_INCLUDE) &&
!PG(allow_url_include_list) ) {
if (options & REPORT_ERRORS) {
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"URL file-access is disabled in the server configuration");
}
Index: ext/soap/php_xml.c
===================================================================
RCS file: /repository/php-src/ext/soap/php_xml.c,v
retrieving revision 1.31
diff -u -p -r1.31 php_xml.c
--- ext/soap/php_xml.c 17 Jan 2007 00:22:48 -0000 1.31
+++ ext/soap/php_xml.c 17 Jan 2007 00:55:27 -0000
@@ -80,16 +80,19 @@ xmlDocPtr soap_xmlParseFile(const char *
{
xmlParserCtxtPtr ctxt = NULL;
xmlDocPtr ret;
- zend_bool old_allow_url_fopen;
+ char *old_allow_url_fopen_list;
/*
xmlInitParser();
*/
- old_allow_url_fopen = PG(allow_url_fopen);
- zend_alter_ini_entry("allow_url_fopen", sizeof("allow_url_fopen"), "1",
1, PHP_INI_SYSTEM, PHP_INI_STAGE_RUNTIME);
+ old_allow_url_fopen_list = PG(allow_url_fopen_list);
+ if (!old_allow_url_fopen_list) {
+ old_allow_url_fopen_list = "";
+ }
+ zend_alter_ini_entry("allow_url_fopen", sizeof("allow_url_fopen"), "*",
1, PHP_INI_SYSTEM, PHP_INI_STAGE_RUNTIME);
ctxt = xmlCreateFileParserCtxt(filename);
- zend_alter_ini_entry("allow_url_fopen", sizeof("allow_url_fopen"),
old_allow_url_fopen ? "1" : "0", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_RUNTIME);
+ zend_alter_ini_entry("allow_url_fopen", sizeof("allow_url_fopen"),
old_allow_url_fopen_list, strlen(old_allow_url_fopen_list), PHP_INI_SYSTEM,
PHP_INI_STAGE_RUNTIME);
if (ctxt) {
ctxt->keepBlanks = 0;
ctxt->sax->ignorableWhitespace = soap_ignorableWhitespace;
Index: ext/soap/php_http.c
===================================================================
RCS file: /repository/php-src/ext/soap/php_http.c,v
retrieving revision 1.100
diff -u -p -r1.100 php_http.c
--- ext/soap/php_http.c 17 Jan 2007 00:22:48 -0000 1.100
+++ ext/soap/php_http.c 17 Jan 2007 00:55:27 -0000
@@ -232,7 +232,7 @@ int make_http_soap_request(zval *this_p
int content_type_xml = 0;
char *content_encoding;
char *http_msg = NULL;
- zend_bool old_allow_url_fopen;
+ char *old_allow_url_fopen_list;
soap_client_object *client;
if (this_ptr == NULL || Z_TYPE_P(this_ptr) != IS_OBJECT) {
@@ -317,13 +317,16 @@ try_again:
return FALSE;
}
- old_allow_url_fopen = PG(allow_url_fopen);
- zend_alter_ini_entry("allow_url_fopen", sizeof("allow_url_fopen"), "1",
1, PHP_INI_SYSTEM, PHP_INI_STAGE_RUNTIME);
+ old_allow_url_fopen_list = PG(allow_url_fopen_list);
+ if (!old_allow_url_fopen_list) {
+ old_allow_url_fopen_list = "";
+ }
+ zend_alter_ini_entry("allow_url_fopen", sizeof("allow_url_fopen"), "*",
1, PHP_INI_SYSTEM, PHP_INI_STAGE_RUNTIME);
if (use_ssl && php_stream_locate_url_wrapper("https://", NULL,
STREAM_LOCATE_WRAPPERS_ONLY TSRMLS_CC) == NULL) {
php_url_free(phpurl);
if (request != buf) {efree(request);}
add_soap_fault(this_ptr, "HTTP", "SSL support is not available
in this build", NULL, NULL TSRMLS_CC);
- zend_alter_ini_entry("allow_url_fopen",
sizeof("allow_url_fopen"), old_allow_url_fopen ? "1" : "0", 1, PHP_INI_SYSTEM,
PHP_INI_STAGE_RUNTIME);
+ zend_alter_ini_entry("allow_url_fopen",
sizeof("allow_url_fopen"), old_allow_url_fopen_list,
strlen(old_allow_url_fopen_list), PHP_INI_SYSTEM, PHP_INI_STAGE_RUNTIME);
return FALSE;
}
@@ -376,11 +379,11 @@ try_again:
php_url_free(phpurl);
if (request != buf) {efree(request);}
add_soap_fault(this_ptr, "HTTP", "Could not connect to
host", NULL, NULL TSRMLS_CC);
- zend_alter_ini_entry("allow_url_fopen",
sizeof("allow_url_fopen"), old_allow_url_fopen ? "1" : "0", 1, PHP_INI_SYSTEM,
PHP_INI_STAGE_RUNTIME);
+ zend_alter_ini_entry("allow_url_fopen",
sizeof("allow_url_fopen"), old_allow_url_fopen_list,
strlen(old_allow_url_fopen_list), PHP_INI_SYSTEM, PHP_INI_STAGE_RUNTIME);
return FALSE;
}
}
- zend_alter_ini_entry("allow_url_fopen", sizeof("allow_url_fopen"),
old_allow_url_fopen ? "1" : "0", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_RUNTIME);
+ zend_alter_ini_entry("allow_url_fopen", sizeof("allow_url_fopen"),
old_allow_url_fopen_list, strlen(old_allow_url_fopen_list), PHP_INI_SYSTEM,
PHP_INI_STAGE_RUNTIME);
if (stream) {
if (client->url) {
--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php