summaryrefslogtreecommitdiff
path: root/sapi/fpm
diff options
context:
space:
mode:
authorIgor Pashev <pashev.igor@gmail.com>2015-02-20 22:25:42 +0300
committerIgor Pashev <pashev.igor@gmail.com>2015-02-20 22:25:42 +0300
commit71d41ca6bb3a9d888b39f34a30f994ac1cf88873 (patch)
tree3a11365b77b332c078440204fb82549d34237a35 /sapi/fpm
parent2d50280dd013556e51b6f275ca965fe7b530029b (diff)
parent1ceec3a053647865493ab417d3ce401b9bc42450 (diff)
downloadphp-71d41ca6bb3a9d888b39f34a30f994ac1cf88873.tar.gz
Merge branch 'master-5.6' of git://anonscm.debian.org/pkg-php/php
Conflicts: debian/changelog debian/patches/series
Diffstat (limited to 'sapi/fpm')
-rw-r--r--sapi/fpm/config.m414
-rw-r--r--sapi/fpm/fpm/events/devpoll.c2
-rw-r--r--sapi/fpm/fpm/events/devpoll.h2
-rw-r--r--sapi/fpm/fpm/events/epoll.c2
-rw-r--r--sapi/fpm/fpm/events/epoll.h2
-rw-r--r--sapi/fpm/fpm/events/kqueue.c2
-rw-r--r--sapi/fpm/fpm/events/kqueue.h2
-rw-r--r--sapi/fpm/fpm/events/poll.c2
-rw-r--r--sapi/fpm/fpm/events/poll.h2
-rw-r--r--sapi/fpm/fpm/events/port.c2
-rw-r--r--sapi/fpm/fpm/events/port.h2
-rw-r--r--sapi/fpm/fpm/events/select.c2
-rw-r--r--sapi/fpm/fpm/events/select.h2
-rw-r--r--sapi/fpm/fpm/fastcgi.c107
-rw-r--r--sapi/fpm/fpm/fastcgi.h4
-rw-r--r--sapi/fpm/fpm/fpm_conf.c21
-rw-r--r--sapi/fpm/fpm/fpm_conf.h6
-rw-r--r--sapi/fpm/fpm/fpm_env.c2
-rw-r--r--sapi/fpm/fpm/fpm_log.c4
-rw-r--r--sapi/fpm/fpm/fpm_main.c23
-rw-r--r--sapi/fpm/fpm/fpm_sockets.c117
-rw-r--r--sapi/fpm/fpm/fpm_sockets.h6
-rw-r--r--sapi/fpm/fpm/fpm_stdio.c40
-rw-r--r--sapi/fpm/fpm/fpm_unix.c187
-rw-r--r--sapi/fpm/fpm/fpm_unix.h3
-rw-r--r--sapi/fpm/fpm/fpm_worker_pool.c2
-rw-r--r--sapi/fpm/fpm/fpm_worker_pool.h4
-rw-r--r--sapi/fpm/php-fpm.8.in21
-rw-r--r--sapi/fpm/php-fpm.conf.in16
-rw-r--r--sapi/fpm/tests/002.phpt14
-rw-r--r--sapi/fpm/tests/003.phpt51
-rw-r--r--sapi/fpm/tests/004.phpt59
-rw-r--r--sapi/fpm/tests/005.phpt57
-rw-r--r--sapi/fpm/tests/006.phpt57
-rw-r--r--sapi/fpm/tests/007.phpt68
-rw-r--r--sapi/fpm/tests/008.phpt84
-rw-r--r--sapi/fpm/tests/009.phpt53
-rw-r--r--sapi/fpm/tests/010.phpt84
-rw-r--r--sapi/fpm/tests/011.phpt53
-rw-r--r--sapi/fpm/tests/012.phpt79
-rw-r--r--sapi/fpm/tests/013.phpt54
-rw-r--r--sapi/fpm/tests/014.phpt54
-rw-r--r--sapi/fpm/tests/015.phpt91
-rw-r--r--sapi/fpm/tests/016.phpt87
-rw-r--r--sapi/fpm/tests/017.phpt67
-rw-r--r--sapi/fpm/tests/019.phpt79
-rw-r--r--sapi/fpm/tests/020.phpt75
-rw-r--r--sapi/fpm/tests/021-uds-acl.phpt89
-rw-r--r--sapi/fpm/tests/fcgi.inc592
-rw-r--r--sapi/fpm/tests/include.inc34
50 files changed, 2343 insertions, 139 deletions
diff --git a/sapi/fpm/config.m4 b/sapi/fpm/config.m4
index 9c10aa6be..f87776aa2 100644
--- a/sapi/fpm/config.m4
+++ b/sapi/fpm/config.m4
@@ -583,6 +583,9 @@ if test "$PHP_FPM" != "no"; then
PHP_ARG_WITH(fpm-systemd,,
[ --with-fpm-systemd Activate systemd integration], no, no)
+ PHP_ARG_WITH(fpm-acl,,
+ [ --with-fpm-acl Use POSIX Access Control Lists], no, no)
+
if test "$PHP_FPM_SYSTEMD" != "no" ; then
if test -z "$PKG_CONFIG"; then
AC_PATH_PROG(PKG_CONFIG, pkg-config, no)
@@ -624,6 +627,17 @@ if test "$PHP_FPM" != "no"; then
else
php_fpm_systemd=simple
fi
+
+ if test "$PHP_FPM_ACL" != "no" ; then
+ AC_CHECK_HEADERS([sys/acl.h])
+ AC_CHECK_LIB(acl, acl_free, [
+ PHP_ADD_LIBRARY(acl)
+ AC_DEFINE(HAVE_FPM_ACL, 1, [ POSIX Access Control List ])
+ ],[
+ AC_MSG_ERROR(libacl required not found)
+ ])
+ fi
+
PHP_SUBST_OLD(php_fpm_systemd)
AC_DEFINE_UNQUOTED(PHP_FPM_SYSTEMD, "$php_fpm_systemd", [fpm systemd service type])
diff --git a/sapi/fpm/fpm/events/devpoll.c b/sapi/fpm/fpm/events/devpoll.c
index 3fa7cd454..c8a1defab 100644
--- a/sapi/fpm/fpm/events/devpoll.c
+++ b/sapi/fpm/fpm/events/devpoll.c
@@ -2,7 +2,7 @@
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
- | Copyright (c) 1997-2014 The PHP Group |
+ | Copyright (c) 1997-2015 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
diff --git a/sapi/fpm/fpm/events/devpoll.h b/sapi/fpm/fpm/events/devpoll.h
index 2753cb5ab..717bbe94f 100644
--- a/sapi/fpm/fpm/events/devpoll.h
+++ b/sapi/fpm/fpm/events/devpoll.h
@@ -2,7 +2,7 @@
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
- | Copyright (c) 1997-2014 The PHP Group |
+ | Copyright (c) 1997-2015 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
diff --git a/sapi/fpm/fpm/events/epoll.c b/sapi/fpm/fpm/events/epoll.c
index b55cb44b1..c50da87df 100644
--- a/sapi/fpm/fpm/events/epoll.c
+++ b/sapi/fpm/fpm/events/epoll.c
@@ -2,7 +2,7 @@
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
- | Copyright (c) 1997-2014 The PHP Group |
+ | Copyright (c) 1997-2015 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
diff --git a/sapi/fpm/fpm/events/epoll.h b/sapi/fpm/fpm/events/epoll.h
index f89e59a5f..eb4bd72af 100644
--- a/sapi/fpm/fpm/events/epoll.h
+++ b/sapi/fpm/fpm/events/epoll.h
@@ -2,7 +2,7 @@
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
- | Copyright (c) 1997-2014 The PHP Group |
+ | Copyright (c) 1997-2015 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
diff --git a/sapi/fpm/fpm/events/kqueue.c b/sapi/fpm/fpm/events/kqueue.c
index 9fde33842..2188cd298 100644
--- a/sapi/fpm/fpm/events/kqueue.c
+++ b/sapi/fpm/fpm/events/kqueue.c
@@ -2,7 +2,7 @@
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
- | Copyright (c) 1997-2014 The PHP Group |
+ | Copyright (c) 1997-2015 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
diff --git a/sapi/fpm/fpm/events/kqueue.h b/sapi/fpm/fpm/events/kqueue.h
index 16f804652..2109a187d 100644
--- a/sapi/fpm/fpm/events/kqueue.h
+++ b/sapi/fpm/fpm/events/kqueue.h
@@ -2,7 +2,7 @@
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
- | Copyright (c) 1997-2014 The PHP Group |
+ | Copyright (c) 1997-2015 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
diff --git a/sapi/fpm/fpm/events/poll.c b/sapi/fpm/fpm/events/poll.c
index d49661413..6e2d014cf 100644
--- a/sapi/fpm/fpm/events/poll.c
+++ b/sapi/fpm/fpm/events/poll.c
@@ -2,7 +2,7 @@
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
- | Copyright (c) 1997-2014 The PHP Group |
+ | Copyright (c) 1997-2015 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
diff --git a/sapi/fpm/fpm/events/poll.h b/sapi/fpm/fpm/events/poll.h
index 4a05755d0..21a2b0fd9 100644
--- a/sapi/fpm/fpm/events/poll.h
+++ b/sapi/fpm/fpm/events/poll.h
@@ -2,7 +2,7 @@
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
- | Copyright (c) 1997-2014 The PHP Group |
+ | Copyright (c) 1997-2015 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
diff --git a/sapi/fpm/fpm/events/port.c b/sapi/fpm/fpm/events/port.c
index 0e6a88055..12e3678b7 100644
--- a/sapi/fpm/fpm/events/port.c
+++ b/sapi/fpm/fpm/events/port.c
@@ -2,7 +2,7 @@
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
- | Copyright (c) 1997-2014 The PHP Group |
+ | Copyright (c) 1997-2015 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
diff --git a/sapi/fpm/fpm/events/port.h b/sapi/fpm/fpm/events/port.h
index f127b8cda..a5771fab5 100644
--- a/sapi/fpm/fpm/events/port.h
+++ b/sapi/fpm/fpm/events/port.h
@@ -2,7 +2,7 @@
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
- | Copyright (c) 1997-2014 The PHP Group |
+ | Copyright (c) 1997-2015 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
diff --git a/sapi/fpm/fpm/events/select.c b/sapi/fpm/fpm/events/select.c
index cf3331c04..e59ecd4a4 100644
--- a/sapi/fpm/fpm/events/select.c
+++ b/sapi/fpm/fpm/events/select.c
@@ -2,7 +2,7 @@
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
- | Copyright (c) 1997-2014 The PHP Group |
+ | Copyright (c) 1997-2015 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
diff --git a/sapi/fpm/fpm/events/select.h b/sapi/fpm/fpm/events/select.h
index 74f7be2bc..fe6d30edc 100644
--- a/sapi/fpm/fpm/events/select.h
+++ b/sapi/fpm/fpm/events/select.h
@@ -2,7 +2,7 @@
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
- | Copyright (c) 1997-2014 The PHP Group |
+ | Copyright (c) 1997-2015 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
diff --git a/sapi/fpm/fpm/fastcgi.c b/sapi/fpm/fpm/fastcgi.c
index d77b6f8ca..8b081b2be 100644
--- a/sapi/fpm/fpm/fastcgi.c
+++ b/sapi/fpm/fpm/fastcgi.c
@@ -2,7 +2,7 @@
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
- | Copyright (c) 1997-2014 The PHP Group |
+ | Copyright (c) 1997-2015 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
@@ -37,8 +37,6 @@
#include <windows.h>
- typedef unsigned int in_addr_t;
-
struct sockaddr_un {
short sun_family;
char sun_path[MAXPATHLEN];
@@ -137,13 +135,14 @@ typedef union _sa_t {
struct sockaddr sa;
struct sockaddr_un sa_unix;
struct sockaddr_in sa_inet;
+ struct sockaddr_in6 sa_inet6;
} sa_t;
static HashTable fcgi_mgmt_vars;
static int is_initialized = 0;
static int in_shutdown = 0;
-static in_addr_t *allowed_clients = NULL;
+static sa_t *allowed_clients = NULL;
static sa_t client_sa;
@@ -257,7 +256,7 @@ void fcgi_set_allowed_clients(char *ip)
cur++;
}
if (allowed_clients) free(allowed_clients);
- allowed_clients = malloc(sizeof(in_addr_t) * (n+2));
+ allowed_clients = malloc(sizeof(sa_t) * (n+2));
n = 0;
cur = ip;
while (cur) {
@@ -266,15 +265,23 @@ void fcgi_set_allowed_clients(char *ip)
*end = 0;
end++;
}
- allowed_clients[n] = inet_addr(cur);
- if (allowed_clients[n] == INADDR_NONE) {
+ if (inet_pton(AF_INET, cur, &allowed_clients[n].sa_inet.sin_addr)>0) {
+ allowed_clients[n].sa.sa_family = AF_INET;
+ n++;
+ } else if (inet_pton(AF_INET6, cur, &allowed_clients[n].sa_inet6.sin6_addr)>0) {
+ allowed_clients[n].sa.sa_family = AF_INET6;
+ n++;
+ } else {
zlog(ZLOG_ERROR, "Wrong IP address '%s' in listen.allowed_clients", cur);
}
- n++;
cur = end;
}
- allowed_clients[n] = INADDR_NONE;
+ allowed_clients[n].sa.sa_family = 0;
free(ip);
+ if (!n) {
+ zlog(ZLOG_ERROR, "There are no allowed addresses for this pool");
+ /* don't clear allowed_clients as it will create an "open for all" security issue */
+ }
}
}
@@ -759,6 +766,43 @@ void fcgi_close(fcgi_request *req, int force, int destroy)
}
}
+static int fcgi_is_allowed() {
+ int i;
+
+ if (client_sa.sa.sa_family == AF_UNIX) {
+ return 1;
+ }
+ if (!allowed_clients) {
+ return 1;
+ }
+ if (client_sa.sa.sa_family == AF_INET) {
+ for (i=0 ; allowed_clients[i].sa.sa_family ; i++) {
+ if (allowed_clients[i].sa.sa_family == AF_INET
+ && !memcmp(&client_sa.sa_inet.sin_addr, &allowed_clients[i].sa_inet.sin_addr, 4)) {
+ return 1;
+ }
+ }
+ }
+ if (client_sa.sa.sa_family == AF_INET6) {
+ for (i=0 ; allowed_clients[i].sa.sa_family ; i++) {
+ if (allowed_clients[i].sa.sa_family == AF_INET6
+ && !memcmp(&client_sa.sa_inet6.sin6_addr, &allowed_clients[i].sa_inet6.sin6_addr, 12)) {
+ return 1;
+ }
+#ifdef IN6_IS_ADDR_V4MAPPED
+ if (allowed_clients[i].sa.sa_family == AF_INET
+ && IN6_IS_ADDR_V4MAPPED(&client_sa.sa_inet6.sin6_addr)
+ && !memcmp(((char *)&client_sa.sa_inet6.sin6_addr)+12, &allowed_clients[i].sa_inet.sin_addr, 4)) {
+ return 1;
+ }
+#endif
+ }
+ }
+
+ zlog(ZLOG_ERROR, "Connection disallowed: IP address '%s' has been dropped.", fcgi_get_last_client_ip());
+ return 0;
+}
+
int fcgi_accept_request(fcgi_request *req)
{
#ifdef _WIN32
@@ -809,23 +853,10 @@ int fcgi_accept_request(fcgi_request *req)
FCGI_UNLOCK(req->listen_socket);
client_sa = sa;
- if (sa.sa.sa_family == AF_INET && req->fd >= 0 && allowed_clients) {
- int n = 0;
- int allowed = 0;
-
- while (allowed_clients[n] != INADDR_NONE) {
- if (allowed_clients[n] == sa.sa_inet.sin_addr.s_addr) {
- allowed = 1;
- break;
- }
- n++;
- }
- if (!allowed) {
- zlog(ZLOG_ERROR, "Connection disallowed: IP address '%s' has been dropped.", inet_ntoa(sa.sa_inet.sin_addr));
- closesocket(req->fd);
- req->fd = -1;
- continue;
- }
+ if (req->fd >= 0 && !fcgi_is_allowed()) {
+ closesocket(req->fd);
+ req->fd = -1;
+ continue;
}
}
@@ -944,6 +975,7 @@ int fcgi_flush(fcgi_request *req, int close)
if (safe_write(req, req->out_buf, len) != len) {
req->keep = 0;
+ req->out_pos = req->out_buf;
return 0;
}
@@ -1094,12 +1126,27 @@ void fcgi_free_mgmt_var_cb(void * ptr)
pefree(*var, 1);
}
-char *fcgi_get_last_client_ip() /* {{{ */
+const char *fcgi_get_last_client_ip() /* {{{ */
{
- if (client_sa.sa.sa_family == AF_UNIX) {
- return NULL;
+ static char str[INET6_ADDRSTRLEN];
+
+ /* Ipv4 */
+ if (client_sa.sa.sa_family == AF_INET) {
+ return inet_ntop(client_sa.sa.sa_family, &client_sa.sa_inet.sin_addr, str, INET6_ADDRSTRLEN);
+ }
+#ifdef IN6_IS_ADDR_V4MAPPED
+ /* Ipv4-Mapped-Ipv6 */
+ if (client_sa.sa.sa_family == AF_INET6
+ && IN6_IS_ADDR_V4MAPPED(&client_sa.sa_inet6.sin6_addr)) {
+ return inet_ntop(AF_INET, ((char *)&client_sa.sa_inet6.sin6_addr)+12, str, INET6_ADDRSTRLEN);
}
- return inet_ntoa(client_sa.sa_inet.sin_addr);
+#endif
+ /* Ipv6 */
+ if (client_sa.sa.sa_family == AF_INET6) {
+ return inet_ntop(client_sa.sa.sa_family, &client_sa.sa_inet6.sin6_addr, str, INET6_ADDRSTRLEN);
+ }
+ /* Unix socket */
+ return NULL;
}
/* }}} */
/*
diff --git a/sapi/fpm/fpm/fastcgi.h b/sapi/fpm/fpm/fastcgi.h
index 34f9eef9d..ccea6c2b5 100644
--- a/sapi/fpm/fpm/fastcgi.h
+++ b/sapi/fpm/fpm/fastcgi.h
@@ -2,7 +2,7 @@
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
- | Copyright (c) 1997-2014 The PHP Group |
+ | Copyright (c) 1997-2015 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
@@ -133,7 +133,7 @@ int fcgi_flush(fcgi_request *req, int close);
void fcgi_set_mgmt_var(const char * name, size_t name_len, const char * value, size_t value_len);
void fcgi_free_mgmt_var_cb(void * ptr);
-char *fcgi_get_last_client_ip();
+const char *fcgi_get_last_client_ip();
/*
* Local variables:
diff --git a/sapi/fpm/fpm/fpm_conf.c b/sapi/fpm/fpm/fpm_conf.c
index 34e048010..d812452e4 100644
--- a/sapi/fpm/fpm/fpm_conf.c
+++ b/sapi/fpm/fpm/fpm_conf.c
@@ -123,6 +123,10 @@ static struct ini_value_parser_s ini_fpm_pool_options[] = {
{ "group", &fpm_conf_set_string, WPO(group) },
{ "listen", &fpm_conf_set_string, WPO(listen_address) },
{ "listen.backlog", &fpm_conf_set_integer, WPO(listen_backlog) },
+#ifdef HAVE_FPM_ACL
+ { "listen.acl_users", &fpm_conf_set_string, WPO(listen_acl_users) },
+ { "listen.acl_groups", &fpm_conf_set_string, WPO(listen_acl_groups) },
+#endif
{ "listen.owner", &fpm_conf_set_string, WPO(listen_owner) },
{ "listen.group", &fpm_conf_set_string, WPO(listen_group) },
{ "listen.mode", &fpm_conf_set_string, WPO(listen_mode) },
@@ -570,6 +574,8 @@ static char *fpm_conf_set_array(zval *key, zval *value, void **config, int conve
} else {
kv->value = strdup(Z_STRVAL_P(value));
if (fpm_conf_expand_pool_name(&kv->value) == -1) {
+ free(kv->key);
+ free(kv);
return "Can't use '$pool' when the pool is not defined";
}
}
@@ -819,7 +825,7 @@ static int fpm_conf_process_all_pools() /* {{{ */
if (config->pm_start_servers <= 0) {
config->pm_start_servers = config->pm_min_spare_servers + ((config->pm_max_spare_servers - config->pm_min_spare_servers) / 2);
- zlog(ZLOG_WARNING, "[pool %s] pm.start_servers is not set. It's been set to %d.", wp->config->name, config->pm_start_servers);
+ zlog(ZLOG_NOTICE, "[pool %s] pm.start_servers is not set. It's been set to %d.", wp->config->name, config->pm_start_servers);
} else if (config->pm_start_servers < config->pm_min_spare_servers || config->pm_start_servers > config->pm_max_spare_servers) {
zlog(ZLOG_ALERT, "[pool %s] pm.start_servers(%d) must not be less than pm.min_spare_servers(%d) and not greater than pm.max_spare_servers(%d)", wp->config->name, config->pm_start_servers, config->pm_min_spare_servers, config->pm_max_spare_servers);
@@ -1159,6 +1165,7 @@ static int fpm_conf_post_process(int force_daemon TSRMLS_DC) /* {{{ */
}
fpm_globals.log_level = fpm_global_config.log_level;
+ zlog_set_level(fpm_globals.log_level);
if (fpm_global_config.process_max < 0) {
zlog(ZLOG_ERROR, "process_max can't be negative");
@@ -1199,15 +1206,15 @@ static int fpm_conf_post_process(int force_daemon TSRMLS_DC) /* {{{ */
return -1;
}
- if (0 > fpm_log_open(0)) {
+ if (0 > fpm_event_pre_init(fpm_global_config.events_mechanism)) {
return -1;
}
- if (0 > fpm_event_pre_init(fpm_global_config.events_mechanism)) {
+ if (0 > fpm_conf_process_all_pools()) {
return -1;
}
- if (0 > fpm_conf_process_all_pools()) {
+ if (0 > fpm_log_open(0)) {
return -1;
}
@@ -1256,7 +1263,7 @@ static void fpm_conf_ini_parser_include(char *inc, void *arg TSRMLS_DC) /* {{{ *
#ifdef HAVE_GLOB
{
g.gl_offs = 0;
- if ((i = glob(inc, GLOB_ERR | GLOB_MARK | GLOB_NOSORT, NULL, &g)) != 0) {
+ if ((i = glob(inc, GLOB_ERR | GLOB_MARK, NULL, &g)) != 0) {
#ifdef GLOB_NOMATCH
if (i == GLOB_NOMATCH) {
zlog(ZLOG_WARNING, "Nothing matches the include pattern '%s' from %s at line %d.", inc, filename, ini_lineno);
@@ -1582,6 +1589,10 @@ static void fpm_conf_dump() /* {{{ */
zlog(ZLOG_NOTICE, "\tgroup = %s", STR2STR(wp->config->group));
zlog(ZLOG_NOTICE, "\tlisten = %s", STR2STR(wp->config->listen_address));
zlog(ZLOG_NOTICE, "\tlisten.backlog = %d", wp->config->listen_backlog);
+#ifdef HAVE_FPM_ACL
+ zlog(ZLOG_NOTICE, "\tlisten.acl_users = %s", STR2STR(wp->config->listen_acl_users));
+ zlog(ZLOG_NOTICE, "\tlisten.acl_groups = %s", STR2STR(wp->config->listen_acl_groups));
+#endif
zlog(ZLOG_NOTICE, "\tlisten.owner = %s", STR2STR(wp->config->listen_owner));
zlog(ZLOG_NOTICE, "\tlisten.group = %s", STR2STR(wp->config->listen_group));
zlog(ZLOG_NOTICE, "\tlisten.mode = %s", STR2STR(wp->config->listen_mode));
diff --git a/sapi/fpm/fpm/fpm_conf.h b/sapi/fpm/fpm/fpm_conf.h
index 12fabe280..540b22795 100644
--- a/sapi/fpm/fpm/fpm_conf.h
+++ b/sapi/fpm/fpm/fpm_conf.h
@@ -58,6 +58,7 @@ struct fpm_worker_pool_config_s {
char *group;
char *listen_address;
int listen_backlog;
+ /* Using chown */
char *listen_owner;
char *listen_group;
char *listen_mode;
@@ -91,6 +92,11 @@ struct fpm_worker_pool_config_s {
#ifdef HAVE_APPARMOR
char *apparmor_hat;
#endif
+#ifdef HAVE_FPM_ACL
+ /* Using Posix ACL */
+ char *listen_acl_users;
+ char *listen_acl_groups;
+#endif
};
struct ini_value_parser_s {
diff --git a/sapi/fpm/fpm/fpm_env.c b/sapi/fpm/fpm/fpm_env.c
index 2ff0bdc0e..3bdb34634 100644
--- a/sapi/fpm/fpm/fpm_env.c
+++ b/sapi/fpm/fpm/fpm_env.c
@@ -212,7 +212,7 @@ int fpm_env_init_main() /* {{{ */
#ifndef HAVE_SETPROCTITLE
#ifdef __linux__
/*
- * This piece of code has been inspirated from nginx and pureftpd code, whic
+ * This piece of code has been inspirated from nginx and pureftpd code, which
* are under BSD licence.
*
* To change the process title in Linux we have to set argv[1] to NULL
diff --git a/sapi/fpm/fpm/fpm_log.c b/sapi/fpm/fpm/fpm_log.c
index 4e1a057db..b0bf32ac1 100644
--- a/sapi/fpm/fpm/fpm_log.c
+++ b/sapi/fpm/fpm/fpm_log.c
@@ -46,6 +46,8 @@ int fpm_log_open(int reopen) /* {{{ */
if (0 > fd) {
zlog(ZLOG_SYSERROR, "failed to open access log (%s)", wp->config->access_log);
return -1;
+ } else {
+ zlog(ZLOG_DEBUG, "open access log (%s)", wp->config->access_log);
}
if (reopen) {
@@ -367,7 +369,7 @@ int fpm_log_write(char *log_format TSRMLS_DC) /* {{{ */
case 'R': /* remote IP address */
if (!test) {
- char *tmp = fcgi_get_last_client_ip();
+ const char *tmp = fcgi_get_last_client_ip();
len2 = snprintf(b, FPM_LOG_BUFFER - len, "%s", tmp ? tmp : "-");
}
break;
diff --git a/sapi/fpm/fpm/fpm_main.c b/sapi/fpm/fpm/fpm_main.c
index cd5492d73..c217d7af4 100644
--- a/sapi/fpm/fpm/fpm_main.c
+++ b/sapi/fpm/fpm/fpm_main.c
@@ -2,7 +2,7 @@
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
- | Copyright (c) 1997-2014 The PHP Group |
+ | Copyright (c) 1997-2015 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
@@ -673,11 +673,15 @@ void sapi_cgi_log_fastcgi(int level, char *message, size_t len)
* - the message is not empty
*/
if (CGIG(fcgi_logging) && request && message && len > 0) {
+ ssize_t ret;
char *buf = malloc(len + 2);
memcpy(buf, message, len);
memcpy(buf + len, "\n", sizeof("\n"));
- fcgi_write(request, FCGI_STDERR, buf, len+1);
+ ret = fcgi_write(request, FCGI_STDERR, buf, len + 1);
free(buf);
+ if (ret < 0) {
+ php_handle_aborted_connection();
+ }
}
}
/* }}} */
@@ -1237,6 +1241,17 @@ static void init_request_info(TSRMLS_D)
SG(request_info).request_uri = orig_script_name;
}
path_info[0] = old;
+ } else if (apache_was_here && env_script_name) {
+ /* Using mod_proxy_fcgi and ProxyPass, apache cannot set PATH_INFO
+ * As we can extract PATH_INFO from PATH_TRANSLATED
+ * it is probably also in SCRIPT_NAME and need to be removed
+ */
+ int snlen = strlen(env_script_name);
+ if (snlen>slen && !strcmp(env_script_name+snlen-slen, path_info)) {
+ _sapi_cgibin_putenv("ORIG_SCRIPT_NAME", orig_script_name TSRMLS_CC);
+ env_script_name[snlen-slen] = 0;
+ SG(request_info).request_uri = _sapi_cgibin_putenv("SCRIPT_NAME", env_script_name TSRMLS_CC);
+ }
}
env_path_info = _sapi_cgibin_putenv("PATH_INFO", path_info TSRMLS_CC);
}
@@ -1737,9 +1752,9 @@ int main(int argc, char *argv[])
SG(request_info).no_headers = 1;
#if ZEND_DEBUG
- php_printf("PHP %s (%s) (built: %s %s) (DEBUG)\nCopyright (c) 1997-2014 The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version());
+ php_printf("PHP %s (%s) (built: %s %s) (DEBUG)\nCopyright (c) 1997-2015 The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version());
#else
- php_printf("PHP %s (%s) (built: %s %s)\nCopyright (c) 1997-2014 The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version());
+ php_printf("PHP %s (%s) (built: %s %s)\nCopyright (c) 1997-2015 The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version());
#endif
php_request_shutdown((void *) 0);
fcgi_shutdown();
diff --git a/sapi/fpm/fpm/fpm_sockets.c b/sapi/fpm/fpm/fpm_sockets.c
index e056565ea..46ce16184 100644
--- a/sapi/fpm/fpm/fpm_sockets.c
+++ b/sapi/fpm/fpm/fpm_sockets.c
@@ -39,29 +39,6 @@ struct listening_socket_s {
static struct fpm_array_s sockets_list;
-static int fpm_sockets_resolve_af_inet(char *node, char *service, struct sockaddr_in *addr) /* {{{ */
-{
- struct addrinfo *res;
- struct addrinfo hints;
- int ret;
-
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = AF_INET;
- ret = getaddrinfo(node, service, &hints, &res);
-
- if (ret != 0) {
- zlog(ZLOG_ERROR, "can't resolve hostname '%s%s%s': getaddrinfo said: %s%s%s\n",
- node, service ? ":" : "", service ? service : "",
- gai_strerror(ret), ret == EAI_SYSTEM ? ", system error: " : "", ret == EAI_SYSTEM ? strerror(errno) : "");
- return -1;
- }
-
- *addr = *(struct sockaddr_in *) res->ai_addr;
- freeaddrinfo(res);
- return 0;
-}
-/* }}} */
-
enum { FPM_GET_USE_SOCKET = 1, FPM_STORE_SOCKET = 2, FPM_STORE_USE_SOCKET = 3 };
static void fpm_sockets_cleanup(int which, void *arg) /* {{{ */
@@ -98,14 +75,34 @@ static void fpm_sockets_cleanup(int which, void *arg) /* {{{ */
}
/* }}} */
+static void *fpm_get_in_addr(struct sockaddr *sa) /* {{{ */
+{
+ if (sa->sa_family == AF_INET) {
+ return &(((struct sockaddr_in*)sa)->sin_addr);
+ }
+
+ return &(((struct sockaddr_in6*)sa)->sin6_addr);
+}
+/* }}} */
+
+static int fpm_get_in_port(struct sockaddr *sa) /* {{{ */
+{
+ if (sa->sa_family == AF_INET) {
+ return ntohs(((struct sockaddr_in*)sa)->sin_port);
+ }
+
+ return ntohs(((struct sockaddr_in6*)sa)->sin6_port);
+}
+/* }}} */
+
static int fpm_sockets_hash_op(int sock, struct sockaddr *sa, char *key, int type, int op) /* {{{ */
{
if (key == NULL) {
switch (type) {
case FPM_AF_INET : {
- struct sockaddr_in *sa_in = (struct sockaddr_in *) sa;
- key = alloca(sizeof("xxx.xxx.xxx.xxx:ppppp"));
- sprintf(key, "%u.%u.%u.%u:%u", IPQUAD(&sa_in->sin_addr), (unsigned int) ntohs(sa_in->sin_port));
+ key = alloca(INET6_ADDRSTRLEN+10);
+ inet_ntop(sa->sa_family, fpm_get_in_addr(sa), key, INET6_ADDRSTRLEN);
+ sprintf(key+strlen(key), ":%d", fpm_get_in_port(sa));
break;
}
@@ -204,12 +201,9 @@ static int fpm_sockets_new_listening_socket(struct fpm_worker_pool_s *wp, struct
umask(saved_umask);
- if (wp->socket_uid != -1 || wp->socket_gid != -1) {
- if (0 > chown(path, wp->socket_uid, wp->socket_gid)) {
- zlog(ZLOG_SYSERROR, "failed to chown() the socket '%s'", wp->config->listen_address);
- close(sock);
- return -1;
- }
+ if (0 > fpm_unix_set_socket_premissions(wp, path)) {
+ close(sock);
+ return -1;
}
}
@@ -254,11 +248,15 @@ enum fpm_address_domain fpm_sockets_domain_from_address(char *address) /* {{{ */
static int fpm_socket_af_inet_listening_socket(struct fpm_worker_pool_s *wp) /* {{{ */
{
- struct sockaddr_in sa_in;
+ struct addrinfo hints, *servinfo, *p;
char *dup_address = strdup(wp->config->listen_address);
- char *port_str = strchr(dup_address, ':');
+ char *port_str = strrchr(dup_address, ':');
char *addr = NULL;
+ char tmpbuf[INET6_ADDRSTRLEN];
+ int addr_len;
int port = 0;
+ int sock = -1;
+ int status;
if (port_str) { /* this is host:port pair */
*port_str++ = '\0';
@@ -274,23 +272,50 @@ static int fpm_socket_af_inet_listening_socket(struct fpm_worker_pool_s *wp) /*
return -1;
}
- memset(&sa_in, 0, sizeof(sa_in));
+ if (!addr) {
+ /* no address: default documented behavior, all IPv4 addresses */
+ struct sockaddr_in sa_in;
- if (addr) {
- sa_in.sin_addr.s_addr = inet_addr(addr);
- if (sa_in.sin_addr.s_addr == INADDR_NONE) { /* do resolve */
- if (0 > fpm_sockets_resolve_af_inet(addr, NULL, &sa_in)) {
- return -1;
+ memset(&sa_in, 0, sizeof(sa_in));
+ sa_in.sin_family = AF_INET;
+ sa_in.sin_port = htons(port);
+ sa_in.sin_addr.s_addr = htonl(INADDR_ANY);
+ free(dup_address);
+ return fpm_sockets_get_listening_socket(wp, (struct sockaddr *) &sa_in, sizeof(struct sockaddr_in));
+ }
+
+ /* strip brackets from address for getaddrinfo */
+ addr_len = strlen(addr);
+ if (addr[0] == '[' && addr[addr_len - 1] == ']') {
+ addr[addr_len - 1] = '\0';
+ addr++;
+ }
+
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+
+ if ((status = getaddrinfo(addr, port_str, &hints, &servinfo)) != 0) {
+ zlog(ZLOG_ERROR, "getaddrinfo: %s\n", gai_strerror(status));
+ free(dup_address);
+ return -1;
+ }
+
+ for (p = servinfo; p != NULL; p = p->ai_next) {
+ inet_ntop(p->ai_family, fpm_get_in_addr(p->ai_addr), tmpbuf, INET6_ADDRSTRLEN);
+ if (sock < 0) {
+ if ((sock = fpm_sockets_get_listening_socket(wp, p->ai_addr, p->ai_addrlen)) != -1) {
+ zlog(ZLOG_DEBUG, "Found address for %s, socket opened on %s", dup_address, tmpbuf);
}
- zlog(ZLOG_NOTICE, "address '%s' resolved as %u.%u.%u.%u", addr, IPQUAD(&sa_in.sin_addr));
+ } else {
+ zlog(ZLOG_WARNING, "Found multiple addresses for %s, %s ignored", dup_address, tmpbuf);
}
- } else {
- sa_in.sin_addr.s_addr = htonl(INADDR_ANY);
}
- sa_in.sin_family = AF_INET;
- sa_in.sin_port = htons(port);
+
free(dup_address);
- return fpm_sockets_get_listening_socket(wp, (struct sockaddr *) &sa_in, sizeof(struct sockaddr_in));
+ freeaddrinfo(servinfo);
+
+ return sock;
}
/* }}} */
diff --git a/sapi/fpm/fpm/fpm_sockets.h b/sapi/fpm/fpm/fpm_sockets.h
index 121c016a7..446c78e41 100644
--- a/sapi/fpm/fpm/fpm_sockets.h
+++ b/sapi/fpm/fpm/fpm_sockets.h
@@ -45,10 +45,4 @@ static inline int fd_set_blocked(int fd, int blocked) /* {{{ */
}
/* }}} */
-#define IPQUAD(sin_addr) \
- (unsigned int) ((unsigned char *) &(sin_addr)->s_addr)[0], \
- (unsigned int) ((unsigned char *) &(sin_addr)->s_addr)[1], \
- (unsigned int) ((unsigned char *) &(sin_addr)->s_addr)[2], \
- (unsigned int) ((unsigned char *) &(sin_addr)->s_addr)[3]
-
#endif
diff --git a/sapi/fpm/fpm/fpm_stdio.c b/sapi/fpm/fpm/fpm_stdio.c
index fcec78435..e28c0cbe7 100644
--- a/sapi/fpm/fpm/fpm_stdio.c
+++ b/sapi/fpm/fpm/fpm_stdio.c
@@ -42,9 +42,28 @@ int fpm_stdio_init_main() /* {{{ */
}
/* }}} */
+static inline int fpm_use_error_log() { /* {{{ */
+ /*
+ * the error_log is NOT used when running in foreground
+ * and from a tty (user looking at output).
+ * So, error_log is used by
+ * - SysV init launch php-fpm as a daemon
+ * - Systemd launch php-fpm in foreground
+ */
+#if HAVE_UNISTD_H
+ if (fpm_global_config.daemonize || (!isatty(STDERR_FILENO) && !fpm_globals.force_stderr)) {
+#else
+ if (fpm_global_config.daemonize) {
+#endif
+ return 1;
+ }
+ return 0;
+}
+
+/* }}} */
int fpm_stdio_init_final() /* {{{ */
{
- if (fpm_global_config.daemonize) {
+ if (fpm_use_error_log()) {
/* prevent duping if logging to syslog */
if (fpm_globals.error_log_fd > 0 && fpm_globals.error_log_fd != STDERR_FILENO) {
@@ -67,6 +86,11 @@ int fpm_stdio_init_child(struct fpm_worker_pool_s *wp) /* {{{ */
closelog(); /* ensure to close syslog not to interrupt with PHP syslog code */
} else
#endif
+
+ /* Notice: child cannot use master error_log
+ * because not aware when being reopen
+ * else, should use if (!fpm_use_error_log())
+ */
if (fpm_globals.error_log_fd > 0) {
close(fpm_globals.error_log_fd);
}
@@ -268,11 +292,7 @@ int fpm_stdio_open_error_log(int reopen) /* {{{ */
if (!strcasecmp(fpm_global_config.error_log, "syslog")) {
openlog(fpm_global_config.syslog_ident, LOG_PID | LOG_CONS, fpm_global_config.syslog_facility);
fpm_globals.error_log_fd = ZLOG_SYSLOG;
-#if HAVE_UNISTD_H
- if (fpm_global_config.daemonize || (!isatty(STDERR_FILENO) && !fpm_globals.force_stderr)) {
-#else
- if (fpm_global_config.daemonize) {
-#endif
+ if (fpm_use_error_log()) {
zlog_set_fd(fpm_globals.error_log_fd);
}
return 0;
@@ -286,7 +306,7 @@ int fpm_stdio_open_error_log(int reopen) /* {{{ */
}
if (reopen) {
- if (fpm_global_config.daemonize) {
+ if (fpm_use_error_log()) {
dup2(fd, STDERR_FILENO);
}
@@ -295,11 +315,7 @@ int fpm_stdio_open_error_log(int reopen) /* {{{ */
fd = fpm_globals.error_log_fd; /* for FD_CLOSEXEC to work */
} else {
fpm_globals.error_log_fd = fd;
-#if HAVE_UNISTD_H
- if (fpm_global_config.daemonize || (!isatty(STDERR_FILENO) && !fpm_globals.force_stderr)) {
-#else
- if (fpm_global_config.daemonize) {
-#endif
+ if (fpm_use_error_log()) {
zlog_set_fd(fpm_globals.error_log_fd);
}
}
diff --git a/sapi/fpm/fpm/fpm_unix.c b/sapi/fpm/fpm/fpm_unix.c
index 68978ee75..f0d457348 100644
--- a/sapi/fpm/fpm/fpm_unix.c
+++ b/sapi/fpm/fpm/fpm_unix.c
@@ -21,6 +21,10 @@
#include <sys/apparmor.h>
#endif
+#ifdef HAVE_SYS_ACL_H
+#include <sys/acl.h>
+#endif
+
#include "fpm.h"
#include "fpm_conf.h"
#include "fpm_cleanup.h"
@@ -35,8 +39,12 @@ size_t fpm_pagesize;
int fpm_unix_resolve_socket_premissions(struct fpm_worker_pool_s *wp) /* {{{ */
{
struct fpm_worker_pool_config_s *c = wp->config;
+#ifdef HAVE_FPM_ACL
+ int n;
/* uninitialized */
+ wp->socket_acl = NULL;
+#endif
wp->socket_uid = -1;
wp->socket_gid = -1;
wp->socket_mode = 0660;
@@ -45,6 +53,117 @@ int fpm_unix_resolve_socket_premissions(struct fpm_worker_pool_s *wp) /* {{{ */
return 0;
}
+ if (c->listen_mode && *c->listen_mode) {
+ wp->socket_mode = strtoul(c->listen_mode, 0, 8);
+ }
+
+#ifdef HAVE_FPM_ACL
+ /* count the users and groups configured */
+ n = 0;
+ if (c->listen_acl_users && *c->listen_acl_users) {
+ char *p;
+ n++;
+ for (p=strchr(c->listen_acl_users, ',') ; p ; p=strchr(p+1, ',')) {
+ n++;
+ }
+ }
+ if (c->listen_acl_groups && *c->listen_acl_groups) {
+ char *p;
+ n++;
+ for (p=strchr(c->listen_acl_groups, ',') ; p ; p=strchr(p+1, ',')) {
+ n++;
+ }
+ }
+ /* if ACL configured */
+ if (n) {
+ acl_t acl;
+ acl_entry_t entry;
+ acl_permset_t perm;
+ char *tmp, *p, *end;
+
+ acl = acl_init(n);
+ if (!acl) {
+ zlog(ZLOG_SYSERROR, "[pool %s] cannot allocate ACL", wp->config->name);
+ return -1;
+ }
+ /* Create USER ACL */
+ if (c->listen_acl_users && *c->listen_acl_users) {
+ struct passwd *pwd;
+
+ tmp = estrdup(c->listen_acl_users);
+ for (p=tmp ; p ; p=end) {
+ if ((end = strchr(p, ','))) {
+ *end++ = 0;
+ }
+ pwd = getpwnam(p);
+ if (pwd) {
+ zlog(ZLOG_DEBUG, "[pool %s] user '%s' have uid=%d", wp->config->name, p, pwd->pw_uid);
+ } else {
+ zlog(ZLOG_SYSERROR, "[pool %s] cannot get uid for user '%s'", wp->config->name, p);
+ acl_free(acl);
+ efree(tmp);
+ return -1;
+ }
+ if (0 > acl_create_entry(&acl, &entry) ||
+ 0 > acl_set_tag_type(entry, ACL_USER) ||
+ 0 > acl_set_qualifier(entry, &pwd->pw_uid) ||
+ 0 > acl_get_permset(entry, &perm) ||
+ 0 > acl_clear_perms (perm) ||
+ 0 > acl_add_perm (perm, ACL_READ) ||
+ 0 > acl_add_perm (perm, ACL_WRITE)) {
+ zlog(ZLOG_SYSERROR, "[pool %s] cannot create ACL for user '%s'", wp->config->name, p);
+ acl_free(acl);
+ efree(tmp);
+ return -1;
+ }
+ }
+ efree(tmp);
+ }
+ /* Create GROUP ACL */
+ if (c->listen_acl_groups && *c->listen_acl_groups) {
+ struct group *grp;
+
+ tmp = estrdup(c->listen_acl_groups);
+ for (p=tmp ; p ; p=end) {
+ if ((end = strchr(p, ','))) {
+ *end++ = 0;
+ }
+ grp = getgrnam(p);
+ if (grp) {
+ zlog(ZLOG_DEBUG, "[pool %s] group '%s' have gid=%d", wp->config->name, p, grp->gr_gid);
+ } else {
+ zlog(ZLOG_SYSERROR, "[pool %s] cannot get gid for group '%s'", wp->config->name, p);
+ acl_free(acl);
+ efree(tmp);
+ return -1;
+ }
+ if (0 > acl_create_entry(&acl, &entry) ||
+ 0 > acl_set_tag_type(entry, ACL_GROUP) ||
+ 0 > acl_set_qualifier(entry, &grp->gr_gid) ||
+ 0 > acl_get_permset(entry, &perm) ||
+ 0 > acl_clear_perms (perm) ||
+ 0 > acl_add_perm (perm, ACL_READ) ||
+ 0 > acl_add_perm (perm, ACL_WRITE)) {
+ zlog(ZLOG_SYSERROR, "[pool %s] cannot create ACL for group '%s'", wp->config->name, p);
+ acl_free(acl);
+ efree(tmp);
+ return -1;
+ }
+ }
+ efree(tmp);
+ }
+ if (c->listen_owner && *c->listen_owner) {
+ zlog(ZLOG_WARNING, "[pool %s] ACL set, listen.owner = '%s' is ignored", wp->config->name, c->listen_owner);
+ }
+ if (c->listen_group && *c->listen_group) {
+ zlog(ZLOG_WARNING, "[pool %s] ACL set, listen.group = '%s' is ignored", wp->config->name, c->listen_group);
+ }
+ wp->socket_acl = acl;
+ return 0;
+ }
+ /* When listen.users and listen.groups not configured, continue with standard right */
+#endif
+
if (c->listen_owner && *c->listen_owner) {
struct passwd *pwd;
@@ -69,13 +188,72 @@ int fpm_unix_resolve_socket_premissions(struct fpm_worker_pool_s *wp) /* {{{ */
wp->socket_gid = grp->gr_gid;
}
- if (c->listen_mode && *c->listen_mode) {
- wp->socket_mode = strtoul(c->listen_mode, 0, 8);
+ return 0;
+}
+/* }}} */
+
+int fpm_unix_set_socket_premissions(struct fpm_worker_pool_s *wp, const char *path) /* {{{ */
+{
+#ifdef HAVE_FPM_ACL
+ if (wp->socket_acl) {
+ acl_t aclfile, aclconf;
+ acl_entry_t entryfile, entryconf;
+ int i;
+
+ /* Read the socket ACL */
+ aclconf = wp->socket_acl;
+ aclfile = acl_get_file (path, ACL_TYPE_ACCESS);
+ if (!aclfile) {
+ zlog(ZLOG_SYSERROR, "[pool %s] failed to read the ACL of the socket '%s'", wp->config->name, path);
+ return -1;
+ }
+ /* Copy the new ACL entry from config */
+ for (i=ACL_FIRST_ENTRY ; acl_get_entry(aclconf, i, &entryconf) ; i=ACL_NEXT_ENTRY) {
+ if (0 > acl_create_entry (&aclfile, &entryfile) ||
+ 0 > acl_copy_entry(entryfile, entryconf)) {
+ zlog(ZLOG_SYSERROR, "[pool %s] failed to add entry to the ACL of the socket '%s'", wp->config->name, path);
+ acl_free(aclfile);
+ return -1;
+ }
+ }
+ /* Write the socket ACL */
+ if (0 > acl_calc_mask (&aclfile) ||
+ 0 > acl_valid (aclfile) ||
+ 0 > acl_set_file (path, ACL_TYPE_ACCESS, aclfile)) {
+ zlog(ZLOG_SYSERROR, "[pool %s] failed to write the ACL of the socket '%s'", wp->config->name, path);
+ acl_free(aclfile);
+ return -1;
+ } else {
+ zlog(ZLOG_DEBUG, "[pool %s] ACL of the socket '%s' is set", wp->config->name, path);
+ }
+
+ acl_free(aclfile);
+ return 0;
+ }
+ /* When listen.users and listen.groups not configured, continue with standard right */
+#endif
+
+ if (wp->socket_uid != -1 || wp->socket_gid != -1) {
+ if (0 > chown(path, wp->socket_uid, wp->socket_gid)) {
+ zlog(ZLOG_SYSERROR, "[pool %s] failed to chown() the socket '%s'", wp->config->name, wp->config->listen_address);
+ return -1;
+ }
}
return 0;
}
/* }}} */
+int fpm_unix_free_socket_premissions(struct fpm_worker_pool_s *wp) /* {{{ */
+{
+#ifdef HAVE_FPM_ACL
+ if (wp->socket_acl) {
+ return acl_free(wp->socket_acl);
+ }
+#endif
+ return 0;
+}
+/* }}} */
+
static int fpm_unix_conf_wp(struct fpm_worker_pool_s *wp) /* {{{ */
{
struct passwd *pwd;
@@ -187,7 +365,9 @@ int fpm_unix_init_child(struct fpm_worker_pool_s *wp) /* {{{ */
return -1;
}
} else if (made_chroot) {
- chdir("/");
+ if (0 > chdir("/")) {
+ zlog(ZLOG_WARNING, "[pool %s] failed to chdir(/)", wp->config->name);
+ }
}
if (is_root) {
@@ -396,7 +576,6 @@ int fpm_unix_init_main() /* {{{ */
}
}
- zlog_set_level(fpm_globals.log_level);
return 0;
}
/* }}} */
diff --git a/sapi/fpm/fpm/fpm_unix.h b/sapi/fpm/fpm/fpm_unix.h
index 3451db126..a79559f9e 100644
--- a/sapi/fpm/fpm/fpm_unix.h
+++ b/sapi/fpm/fpm/fpm_unix.h
@@ -8,6 +8,9 @@
#include "fpm_worker_pool.h"
int fpm_unix_resolve_socket_premissions(struct fpm_worker_pool_s *wp);
+int fpm_unix_set_socket_premissions(struct fpm_worker_pool_s *wp, const char *path);
+int fpm_unix_free_socket_premissions(struct fpm_worker_pool_s *wp);
+
int fpm_unix_init_child(struct fpm_worker_pool_s *wp);
int fpm_unix_init_main();
diff --git a/sapi/fpm/fpm/fpm_worker_pool.c b/sapi/fpm/fpm/fpm_worker_pool.c
index ebe1866c8..a0022915c 100644
--- a/sapi/fpm/fpm/fpm_worker_pool.c
+++ b/sapi/fpm/fpm/fpm_worker_pool.c
@@ -15,6 +15,7 @@
#include "fpm_shm.h"
#include "fpm_scoreboard.h"
#include "fpm_conf.h"
+#include "fpm_unix.h"
struct fpm_worker_pool_s *fpm_worker_all_pools;
@@ -29,6 +30,7 @@ void fpm_worker_pool_free(struct fpm_worker_pool_s *wp) /* {{{ */
if (wp->home) {
free(wp->home);
}
+ fpm_unix_free_socket_premissions(wp);
free(wp);
}
/* }}} */
diff --git a/sapi/fpm/fpm/fpm_worker_pool.h b/sapi/fpm/fpm/fpm_worker_pool.h
index 05c993de4..6b2bc908d 100644
--- a/sapi/fpm/fpm/fpm_worker_pool.h
+++ b/sapi/fpm/fpm/fpm_worker_pool.h
@@ -42,6 +42,10 @@ struct fpm_worker_pool_s {
/* for ondemand PM */
struct fpm_event_s *ondemand_event;
int socket_event_set;
+
+#ifdef HAVE_FPM_ACL
+ void *socket_acl;
+#endif
};
struct fpm_worker_pool_s *fpm_worker_pool_alloc();
diff --git a/sapi/fpm/php-fpm.8.in b/sapi/fpm/php-fpm.8.in
index b02aa25ba..3a697c6a6 100644
--- a/sapi/fpm/php-fpm.8.in
+++ b/sapi/fpm/php-fpm.8.in
@@ -84,6 +84,13 @@ Version number
Specify alternative prefix path (the default is @php_fpm_prefix@)
.TP
.PD 0
+.B \-\-pid \fIfile\fP
+.TP
+.PD 1
+.B \-g
+Specify the PID file location.
+.TP
+.PD 0
.B \-\-fpm\-config \fIfile\fP
.TP
.PD 1
@@ -113,12 +120,18 @@ Force to run in background and ignore daemonize option from configuration file.
Force to stay in foreground and ignore daemonize option from configuration file.
.TP
.PD 0
-.B \-\-zend\-extension \fIfile\fP
+.B \-\-force-stderr
.TP
.PD 1
-.B \-z \fIfile\fP
-Load Zend extension
-.IR file
+.B \-O
+Force output to stderr in nodaemonize even if stderr is not a TTY.
+.TP
+.PD 0
+.B \-\-allow\-to\-run\-as\-root
+.TP
+.PD 1
+.B \-R
+Allow pool to run as root (disabled by default)
.SH FILES
.TP 15
.B php-fpm.conf
diff --git a/sapi/fpm/php-fpm.conf.in b/sapi/fpm/php-fpm.conf.in
index c5f4abc59..dd037db76 100644
--- a/sapi/fpm/php-fpm.conf.in
+++ b/sapi/fpm/php-fpm.conf.in
@@ -131,6 +131,7 @@
; Per pool prefix
; It only applies on the following directives:
+; - 'access.log'
; - 'slowlog'
; - 'listen' (unixsocket)
; - 'chroot'
@@ -150,10 +151,14 @@ group = @php_fpm_group@
; The address on which to accept FastCGI requests.
; Valid syntaxes are:
-; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific address on
+; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on
; a specific port;
-; 'port' - to listen on a TCP socket to all addresses on a
+; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on
+; a specific port;
+; 'port' - to listen on a TCP socket to all IPv4 addresses on a
; specific port;
+; '[::]:port' - to listen on a TCP socket to all addresses
+; (IPv6 and IPv4-mapped) on a specific port;
; '/path/to/unix/socket' - to listen on a unix socket.
; Note: This value is mandatory.
listen = 127.0.0.1:9000
@@ -170,8 +175,13 @@ listen = 127.0.0.1:9000
;listen.owner = @php_fpm_user@
;listen.group = @php_fpm_group@
;listen.mode = 0660
+; When POSIX Access Control Lists are supported you can set them using
+; these options, value is a comma separated list of user/group names.
+; When set, listen.owner and listen.group are ignored
+;listen.acl_users =
+;listen.acl_groups =
-; List of ipv4 addresses of FastCGI clients which are allowed to connect.
+; List of addresses (IPv4/IPv6) of FastCGI clients which are allowed to connect.
; Equivalent to the FCGI_WEB_SERVER_ADDRS environment variable in the original
; PHP FCGI (5.2.2+). Makes sense only with a tcp listening socket. Each address
; must be separated by a comma. If this value is left blank, connections will be
diff --git a/sapi/fpm/tests/002.phpt b/sapi/fpm/tests/002.phpt
index 2ef6cedc3..89e431849 100644
--- a/sapi/fpm/tests/002.phpt
+++ b/sapi/fpm/tests/002.phpt
@@ -8,12 +8,13 @@ FPM: Startup and connect
include "include.inc";
$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$port = 9000+PHP_INT_SIZE;
$cfg = <<<EOT
[global]
error_log = $logfile
[unconfined]
-listen = 127.0.0.1:9000
+listen = 127.0.0.1:$port
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
@@ -23,10 +24,9 @@ EOT;
$fpm = run_fpm($cfg, $tail);
if (is_resource($fpm)) {
- var_dump(fgets($tail));
- var_dump(fgets($tail));
+ fpm_display_log($tail, 2);
$i = 0;
- while (($i++ < 30) && !($fp = @fsockopen('127.0.0.1', 9000))) {
+ while (($i++ < 30) && !($fp = @fsockopen('127.0.0.1', $port))) {
usleep(10000);
}
if ($fp) {
@@ -41,10 +41,8 @@ if (is_resource($fpm)) {
?>
--EXPECTF--
-string(%d) "[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
-"
-string(%d) "[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
-"
+[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
Done
--CLEAN--
<?php
diff --git a/sapi/fpm/tests/003.phpt b/sapi/fpm/tests/003.phpt
new file mode 100644
index 000000000..f928626ec
--- /dev/null
+++ b/sapi/fpm/tests/003.phpt
@@ -0,0 +1,51 @@
+--TEST--
+FPM: Test IPv6 support
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$port = 9000+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+[unconfined]
+listen = [::1]:$port
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ $i = 0;
+ while (($i++ < 30) && !($fp = fsockopen('[::1]', $port))) {
+ usleep(10000);
+ }
+ if ($fp) {
+ echo "Done\n";
+ fclose($fp);
+ }
+ proc_terminate($fpm);
+ stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+--EXPECTF--
+[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
+Done
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+?>
diff --git a/sapi/fpm/tests/004.phpt b/sapi/fpm/tests/004.phpt
new file mode 100644
index 000000000..2a4d666c6
--- /dev/null
+++ b/sapi/fpm/tests/004.phpt
@@ -0,0 +1,59 @@
+--TEST--
+FPM: Test IPv4/IPv6 support
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$port = 9000+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+[unconfined]
+listen = [::]:$port
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ $i = 0;
+ while (($i++ < 30) && !($fp = @fsockopen('127.0.0.1', $port))) {
+ usleep(10000);
+ }
+ if ($fp) {
+ echo "Done IPv4\n";
+ fclose($fp);
+ }
+ while (($i++ < 30) && !($fp = @fsockopen('[::1]', $port))) {
+ usleep(10000);
+ }
+ if ($fp) {
+ echo "Done IPv6\n";
+ fclose($fp);
+ }
+ proc_terminate($fpm);
+ stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+--EXPECTF--
+[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
+Done IPv4
+Done IPv6
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+?>
diff --git a/sapi/fpm/tests/005.phpt b/sapi/fpm/tests/005.phpt
new file mode 100644
index 000000000..c565c2a9e
--- /dev/null
+++ b/sapi/fpm/tests/005.phpt
@@ -0,0 +1,57 @@
+--TEST--
+FPM: Test IPv4 allowed clients
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$port = 9000+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+[unconfined]
+listen = [::]:$port
+listen.allowed_clients = 127.0.0.1
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ try {
+ run_request('127.0.0.1', $port);
+ echo "IPv4 ok\n";
+ } catch (Exception $e) {
+ echo "IPv4 error\n";
+ }
+ try {
+ run_request('[::1]', $port);
+ echo "IPv6 ok\n";
+ } catch (Exception $e) {
+ echo "IPv6 error\n";
+ }
+ proc_terminate($fpm);
+ stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+--EXPECTF--
+[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
+IPv4 ok
+IPv6 error
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+?>
diff --git a/sapi/fpm/tests/006.phpt b/sapi/fpm/tests/006.phpt
new file mode 100644
index 000000000..a12ca253d
--- /dev/null
+++ b/sapi/fpm/tests/006.phpt
@@ -0,0 +1,57 @@
+--TEST--
+FPM: Test IPv6 allowed clients (bug #68428)
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$port = 9000+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+[unconfined]
+listen = [::]:$port
+listen.allowed_clients = ::1
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ try {
+ run_request('127.0.0.1', $port);
+ echo "IPv4 ok\n";
+ } catch (Exception $e) {
+ echo "IPv4 error\n";
+ }
+ try {
+ run_request('[::1]', $port);
+ echo "IPv6 ok\n";
+ } catch (Exception $e) {
+ echo "IPv6 error\n";
+ }
+ proc_terminate($fpm);
+ stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+--EXPECTF--
+[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
+IPv4 error
+IPv6 ok
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+?>
diff --git a/sapi/fpm/tests/007.phpt b/sapi/fpm/tests/007.phpt
new file mode 100644
index 000000000..0d817907c
--- /dev/null
+++ b/sapi/fpm/tests/007.phpt
@@ -0,0 +1,68 @@
+--TEST--
+FPM: Test IPv6 all addresses and access_log (bug #68421)
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$accfile = dirname(__FILE__).'/php-fpm.acc.tmp';
+$port = 9000+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+[unconfined]
+listen = [::]:$port
+access.log = $accfile
+ping.path = /ping
+ping.response = pong
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ try {
+ var_dump(strpos(run_request('127.0.0.1', $port), 'pong'));
+ echo "IPv4 ok\n";
+ } catch (Exception $e) {
+ echo "IPv4 error\n";
+ }
+ try {
+ var_dump(strpos(run_request('[::1]', $port), 'pong'));
+ echo "IPv6 ok\n";
+ } catch (Exception $e) {
+ echo "IPv6 error\n";
+ }
+ proc_terminate($fpm);
+ stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+
+ echo file_get_contents($accfile);
+}
+
+?>
+--EXPECTF--
+[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
+int(%d)
+IPv4 ok
+int(%d)
+IPv6 ok
+127.0.0.1 %s "GET /ping" 200
+::1 %s "GET /ping" 200
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+ $accfile = dirname(__FILE__).'/php-fpm.acc.tmp';
+ @unlink($accfile);
+?>
diff --git a/sapi/fpm/tests/008.phpt b/sapi/fpm/tests/008.phpt
new file mode 100644
index 000000000..60f1ea6eb
--- /dev/null
+++ b/sapi/fpm/tests/008.phpt
@@ -0,0 +1,84 @@
+--TEST--
+FPM: Test multi pool (dynamic + ondemand + static) (bug #68423)
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$port1 = 9000+PHP_INT_SIZE;
+$port2 = 9001+PHP_INT_SIZE;
+$port3 = 9002+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+[pool_dynamic]
+listen = 127.0.0.1:$port1
+ping.path = /ping
+ping.response = pong-dynamic
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+[poold_ondemand]
+listen = 127.0.0.1:$port2
+ping.path = /ping
+ping.response = pong-on-demand
+pm = ondemand
+pm.max_children = 2
+pm.process_idle_timeout = 10
+[pool_static]
+listen = 127.0.0.1:$port3
+ping.path = /ping
+ping.response = pong-static
+pm = static
+pm.max_children = 2
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ try {
+ var_dump(strpos(run_request('127.0.0.1', $port1), 'pong-dynamic'));
+ echo "Dynamic ok\n";
+ } catch (Exception $e) {
+ echo "Dynamic error\n";
+ }
+ try {
+ var_dump(strpos(run_request('127.0.0.1', $port2), 'pong-on-demand'));
+ echo "OnDemand ok\n";
+ } catch (Exception $e) {
+ echo "OnDemand error\n";
+ }
+ try {
+ var_dump(strpos(run_request('127.0.0.1', $port3), 'pong-static'));
+ echo "Static ok\n";
+ } catch (Exception $e) {
+ echo "Static error\n";
+ }
+
+ proc_terminate($fpm);
+ stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+--EXPECTF--
+[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
+int(%d)
+Dynamic ok
+int(%d)
+OnDemand ok
+int(%d)
+Static ok
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+?>
diff --git a/sapi/fpm/tests/009.phpt b/sapi/fpm/tests/009.phpt
new file mode 100644
index 000000000..34cdffcf8
--- /dev/null
+++ b/sapi/fpm/tests/009.phpt
@@ -0,0 +1,53 @@
+--TEST--
+FPM: Test Unix Domain Socket
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$socket = dirname(__FILE__).'/php-fpm.sock';
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+[unconfined]
+listen = $socket
+ping.path = /ping
+ping.response = pong
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ try {
+ var_dump(strpos(run_request('unix://'.$socket, -1), 'pong'));
+ echo "UDS ok\n";
+ } catch (Exception $e) {
+ echo "UDS error\n";
+ }
+
+ proc_terminate($fpm);
+ stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+--EXPECTF--
+[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
+int(%d)
+UDS ok
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+?>
diff --git a/sapi/fpm/tests/010.phpt b/sapi/fpm/tests/010.phpt
new file mode 100644
index 000000000..37c778db5
--- /dev/null
+++ b/sapi/fpm/tests/010.phpt
@@ -0,0 +1,84 @@
+--TEST--
+FPM: Test status page
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$port = 9000+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+[unconfined]
+listen = 127.0.0.1:$port
+pm.status_path = /status
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ try {
+ echo run_request('127.0.0.1', $port, '/status');
+
+ $html = run_request('127.0.0.1', $port, '/status', 'html');
+ var_dump(strpos($html, 'text/html') && strpos($html, 'DOCTYPE') && strpos($html, 'PHP-FPM Status Page'));
+
+ $json = run_request('127.0.0.1', $port, '/status', 'json');
+ var_dump(strpos($json, 'application/json') && strpos($json, '"pool":"unconfined"'));
+
+ $xml = run_request('127.0.0.1', $port, '/status', 'xml');
+ var_dump(strpos($xml, 'text/xml') && strpos($xml, '<?xml'));
+
+ echo "IPv4 ok\n";
+ } catch (Exception $e) {
+ echo "IPv4 error\n";
+ }
+
+ proc_terminate($fpm);
+ stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+--EXPECTF--
+[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
+X-Powered-By: PHP/%s
+Expires: %s
+Cache-Control: %s
+Content-type: text/plain%s
+
+pool: unconfined
+process manager: dynamic
+start time: %s
+start since: %d
+accepted conn: 1
+listen queue: 0
+max listen queue: 0
+listen queue len: %d
+idle processes: 1
+active processes: 1
+total processes: 2
+max active processes: 1
+max children reached: 0
+slow requests: 0
+
+bool(true)
+bool(true)
+bool(true)
+IPv4 ok
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+?>
diff --git a/sapi/fpm/tests/011.phpt b/sapi/fpm/tests/011.phpt
new file mode 100644
index 000000000..0b849f873
--- /dev/null
+++ b/sapi/fpm/tests/011.phpt
@@ -0,0 +1,53 @@
+--TEST--
+FPM: Test IPv4 all addresses (bug #68420)
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$port = 9000+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+[unconfined]
+listen = $port
+ping.path = /ping
+ping.response = pong
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ try {
+ var_dump(strpos(run_request('127.0.0.1', $port), 'pong'));
+ echo "IPv4 ok\n";
+ } catch (Exception $e) {
+ echo "IPv4 error\n";
+ }
+
+ proc_terminate($fpm);
+ stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+--EXPECTF--
+[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
+int(%d)
+IPv4 ok
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+?>
diff --git a/sapi/fpm/tests/012.phpt b/sapi/fpm/tests/012.phpt
new file mode 100644
index 000000000..d96c53081
--- /dev/null
+++ b/sapi/fpm/tests/012.phpt
@@ -0,0 +1,79 @@
+--TEST--
+FPM: Test reload configuration (bug #68442)
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$pidfile = dirname(__FILE__).'/php-fpm.pid';
+$port = 9000+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+pid = $pidfile
+[unconfined]
+listen = 127.0.0.1:$port
+ping.path = /ping
+ping.response = pong
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ try {
+ var_dump(strpos(run_request('127.0.0.1', $port), 'pong'));
+ echo "IPv4 ok\n";
+ } catch (Exception $e) {
+ echo "IPv4 error\n";
+ }
+
+ $pid=file_get_contents($pidfile);
+ if ($pid) {
+ exec("kill -USR2 $pid");
+ } else {
+ die("PID not found\n");
+ }
+ fpm_display_log($tail, 5);
+
+ try {
+ var_dump(strpos(run_request('127.0.0.1', $port), 'pong'));
+ echo "IPv4 ok\n";
+ } catch (Exception $e) {
+ echo "IPv4 error\n";
+ }
+
+ proc_terminate($fpm);
+ stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+--EXPECTF--
+[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
+int(%d)
+IPv4 ok
+[%d-%s-%d %d:%d:%d] NOTICE: Reloading in progress ...
+[%d-%s-%d %d:%d:%d] NOTICE: reloading: %s
+[%d-%s-%d %d:%d:%d] NOTICE: using inherited socket fd=%d, "127.0.0.1:%d"
+[%d-%s-%d %d:%d:%d] NOTICE: fpm is running, pid %d
+[%d-%s-%d %d:%d:%d] NOTICE: ready to handle connections
+int(%d)
+IPv4 ok
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+ $pidfile = dirname(__FILE__).'/php-fpm.pid';
+ @unlink($pidfile);
+?>
diff --git a/sapi/fpm/tests/013.phpt b/sapi/fpm/tests/013.phpt
new file mode 100644
index 000000000..8d6a9d1d8
--- /dev/null
+++ b/sapi/fpm/tests/013.phpt
@@ -0,0 +1,54 @@
+--TEST--
+FPM: Test for log_level in fpm_unix_init_main #68381
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$port = 9000+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+log_level = warning
+[unconfined]
+listen = 127.0.0.1:$port
+user = foo
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ $i = 0;
+ while (($i++ < 30) && !($fp = @fsockopen('127.0.0.1', $port))) {
+ usleep(10000);
+ }
+ if ($fp) {
+ echo "Started\n";
+ fclose($fp);
+ }
+ proc_terminate($fpm);
+ if (!feof($tail)) {
+ echo stream_get_contents($tail);
+ }
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+Done
+--EXPECTF--
+Started
+Done
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+?> \ No newline at end of file
diff --git a/sapi/fpm/tests/014.phpt b/sapi/fpm/tests/014.phpt
new file mode 100644
index 000000000..ee0e549cc
--- /dev/null
+++ b/sapi/fpm/tests/014.phpt
@@ -0,0 +1,54 @@
+--TEST--
+FPM: Test for pm.start_servers default calculation message being a notice and not a warning #68458
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$port = 9000+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+log_level = warning
+[unconfined]
+listen = 127.0.0.1:$port
+user = foo
+pm = dynamic
+pm.max_children = 5
+;pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ $i = 0;
+ while (($i++ < 30) && !($fp = @fsockopen('127.0.0.1', $port))) {
+ usleep(10000);
+ }
+ if ($fp) {
+ echo "Started\n";
+ fclose($fp);
+ }
+ proc_terminate($fpm);
+ if (!feof($tail)) {
+ echo stream_get_contents($tail);
+ }
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+Done
+--EXPECTF--
+Started
+Done
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+?> \ No newline at end of file
diff --git a/sapi/fpm/tests/015.phpt b/sapi/fpm/tests/015.phpt
new file mode 100644
index 000000000..c7af5f757
--- /dev/null
+++ b/sapi/fpm/tests/015.phpt
@@ -0,0 +1,91 @@
+--TEST--
+FPM: Test various messages on start, from master and childs
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--XFAIL--
+randomly intermittently failing all the time in CI,
+ERROR: unable to read what child say: Bad file descriptor (9)
+catch_workers_output = yes seems not reliable
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$port1 = 9000+PHP_INT_SIZE;
+$port2 = 9001+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+log_level = notice
+[pool1]
+listen = 127.0.0.1:$port1
+listen.allowed_clients=127.0.0.1
+user = foo
+pm = dynamic
+pm.max_children = 5
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+catch_workers_output = yes
+[pool2]
+listen = 127.0.0.1:$port2
+listen.allowed_clients=xxx
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 1
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+catch_workers_output = yes
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ $i = 0;
+ while (($i++ < 30) && !($fp = @fsockopen('127.0.0.1', $port1))) {
+ usleep(10000);
+ }
+ if ($fp) {
+ echo "Started\n";
+ fclose($fp);
+ }
+ for ($i=0 ; $i<10 ; $i++) {
+ try {
+ run_request('127.0.0.1', $port1);
+ } catch (Exception $e) {
+ echo "Error 1\n";
+ }
+ }
+ try {
+ run_request('127.0.0.1', $port2);
+ } catch (Exception $e) {
+ echo "Error 2\n";
+ }
+ proc_terminate($fpm);
+ if (!feof($tail)) {
+ echo stream_get_contents($tail);
+ }
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+Done
+--EXPECTF--
+Started
+Error 2
+[%s] NOTICE: [pool pool1] pm.start_servers is not set. It's been set to 2.
+[%s] NOTICE: [pool pool1] 'user' directive is ignored when FPM is not running as root
+[%s] NOTICE: fpm is running, pid %d
+[%s] NOTICE: ready to handle connections
+[%s] WARNING: [pool pool2] child %d said into stderr: "ERROR: Wrong IP address 'xxx' in listen.allowed_clients"
+[%s] WARNING: [pool pool2] child %d said into stderr: "ERROR: There are no allowed addresses for this pool"
+[%s] WARNING: [pool pool2] child %d said into stderr: "ERROR: Connection disallowed: IP address '127.0.0.1' has been dropped."
+[%s] NOTICE: Terminating ...
+[%s] NOTICE: exiting, bye-bye!
+Done
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+?> \ No newline at end of file
diff --git a/sapi/fpm/tests/016.phpt b/sapi/fpm/tests/016.phpt
new file mode 100644
index 000000000..1a9e8e757
--- /dev/null
+++ b/sapi/fpm/tests/016.phpt
@@ -0,0 +1,87 @@
+--TEST--
+FPM: Test splited configuration and load order #68391
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = __DIR__.'/php-fpm.log.tmp';
+$logdir = __DIR__.'/conf.d';
+$port = 9000+PHP_INT_SIZE;
+
+// Main configuration
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+log_level = notice
+include = $logdir/*.conf
+EOT;
+
+// Splited configuration
+@mkdir($logdir);
+$i=$port;
+$names = ['cccc', 'aaaa', 'eeee', 'dddd', 'bbbb'];
+foreach($names as $name) {
+ $poolcfg = <<<EOT
+[$name]
+listen = 127.0.0.1:$i
+listen.allowed_clients=127.0.0.1
+user = foo
+pm = ondemand
+pm.max_children = 5
+EOT;
+ file_put_contents("$logdir/$name.conf", $poolcfg);
+ $i++;
+}
+
+// Test
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, count($names)+2);
+ $i=$port;
+ foreach($names as $name) {
+ try {
+ run_request('127.0.0.1', $i++);
+ echo "OK $name\n";
+ } catch (Exception $e) {
+ echo "Error 1\n";
+ }
+ }
+ proc_terminate($fpm);
+ if (!feof($tail)) {
+ echo stream_get_contents($tail);
+ }
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+Done
+--EXPECTF--
+[%s] NOTICE: [pool aaaa] 'user' directive is ignored when FPM is not running as root
+[%s] NOTICE: [pool bbbb] 'user' directive is ignored when FPM is not running as root
+[%s] NOTICE: [pool cccc] 'user' directive is ignored when FPM is not running as root
+[%s] NOTICE: [pool dddd] 'user' directive is ignored when FPM is not running as root
+[%s] NOTICE: [pool eeee] 'user' directive is ignored when FPM is not running as root
+[%s] NOTICE: fpm is running, pid %d
+[%s] NOTICE: ready to handle connections
+OK cccc
+OK aaaa
+OK eeee
+OK dddd
+OK bbbb
+[%s] NOTICE: Terminating ...
+[%s] NOTICE: exiting, bye-bye!
+Done
+--CLEAN--
+<?php
+ $logfile = __DIR__.'/php-fpm.log.tmp';
+ $logdir = __DIR__.'/conf.d';
+ @unlink($logfile);
+ foreach(glob("$logdir/*.conf") as $name) {
+ unlink($name);
+ }
+ @rmdir($logdir);
+?> \ No newline at end of file
diff --git a/sapi/fpm/tests/017.phpt b/sapi/fpm/tests/017.phpt
new file mode 100644
index 000000000..b3de089a7
--- /dev/null
+++ b/sapi/fpm/tests/017.phpt
@@ -0,0 +1,67 @@
+--TEST--
+FPM: Test fastcgi_finish_request function
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = __DIR__.'/php-fpm.log.tmp';
+$srcfile = __DIR__.'/php-fpm.tmp.php';
+$port = 9000+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+[unconfined]
+listen = 127.0.0.1:$port
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 1
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$code = <<<EOT
+<?php
+echo "Test Start\n";
+fastcgi_finish_request();
+echo "Test End\n";
+EOT;
+file_put_contents($srcfile, $code);
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ try {
+ $req = run_request('127.0.0.1', $port, $srcfile);
+ echo strstr($req, "Test Start");
+ echo "Request ok\n";
+ } catch (Exception $e) {
+ echo "Request error\n";
+ }
+ proc_terminate($fpm);
+ echo stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+Done
+--EXPECTF--
+[%s] NOTICE: fpm is running, pid %d
+[%s] NOTICE: ready to handle connections
+Test Start
+
+Request ok
+[%s] NOTICE: Terminating ...
+[%s] NOTICE: exiting, bye-bye!
+Done
+--CLEAN--
+<?php
+ $logfile = __DIR__.'/php-fpm.log.tmp';
+ $srcfile = __DIR__.'/php-fpm.tmp.php';
+ @unlink($logfile);
+ @unlink($srcfile);
+?> \ No newline at end of file
diff --git a/sapi/fpm/tests/019.phpt b/sapi/fpm/tests/019.phpt
new file mode 100644
index 000000000..3ae4b348d
--- /dev/null
+++ b/sapi/fpm/tests/019.phpt
@@ -0,0 +1,79 @@
+--TEST--
+FPM: Test global prefix
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = 'php-fpm.log.tmp';
+$accfile = 'php-fpm.acc.tmp';
+$slwfile = 'php-fpm.slw.tmp';
+$pidfile = 'php-fpm.pid.tmp';
+$port = 9000+PHP_INT_SIZE;
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+pid = $pidfile
+[test]
+listen = 127.0.0.1:$port
+access.log = $accfile
+slowlog = $slwfile;
+request_slowlog_timeout = 1
+ping.path = /ping
+ping.response = pong
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail, '--prefix '.__DIR__);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ try {
+ run_request('127.0.0.1', $port);
+ echo "Ping ok\n";
+ } catch (Exception $e) {
+ echo "Ping error\n";
+ }
+ printf("File %s %s\n", $logfile, (file_exists(__DIR__.'/'.$logfile) ? "exists" : "missing"));
+ printf("File %s %s\n", $accfile, (file_exists(__DIR__.'/'.$accfile) ? "exists" : "missing"));
+ printf("File %s %s\n", $slwfile, (file_exists(__DIR__.'/'.$slwfile) ? "exists" : "missing"));
+ printf("File %s %s\n", $pidfile, (file_exists(__DIR__.'/'.$pidfile) ? "exists" : "missing"));
+
+ proc_terminate($fpm);
+ echo stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+ printf("File %s %s\n", $pidfile, (file_exists(__DIR__.'/'.$pidfile) ? "still exists" : "removed"));
+ readfile(__DIR__.'/'.$accfile);
+}
+
+?>
+--EXPECTF--
+[%s] NOTICE: fpm is running, pid %d
+[%s] NOTICE: ready to handle connections
+Ping ok
+File php-fpm.log.tmp exists
+File php-fpm.acc.tmp exists
+File php-fpm.slw.tmp exists
+File php-fpm.pid.tmp exists
+[%s] NOTICE: Terminating ...
+[%s] NOTICE: exiting, bye-bye!
+File php-fpm.pid.tmp removed
+127.0.0.1 - %s "GET /ping" 200
+--CLEAN--
+<?php
+ $logfile = __DIR__.'/php-fpm.log.tmp';
+ $accfile = __DIR__.'/php-fpm.acc.tmp';
+ $slwfile = __DIR__.'/php-fpm.slw.tmp';
+ $pidfile = __DIR__.'/php-fpm.pid.tmp';
+ @unlink($logfile);
+ @unlink($accfile);
+ @unlink($slwfile);
+ @unlink($pidfile);
+?>
diff --git a/sapi/fpm/tests/020.phpt b/sapi/fpm/tests/020.phpt
new file mode 100644
index 000000000..dbd43d05f
--- /dev/null
+++ b/sapi/fpm/tests/020.phpt
@@ -0,0 +1,75 @@
+--TEST--
+FPM: Test pool prefix
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+include "include.inc";
+
+$prefix = __DIR__;
+$logfile = __DIR__.'/php-fpm.log.tmp';
+$accfile = 'php-fpm.acc.tmp';
+$slwfile = 'php-fpm.slw.tmp';
+$pidfile = __DIR__.'/php-fpm.pid.tmp';
+$port = 9000+PHP_INT_SIZE;
+$cfg = <<<EOT
+
+[global]
+error_log = $logfile
+pid = $pidfile
+[test]
+prefix = $prefix;
+listen = 127.0.0.1:$port
+access.log = $accfile
+slowlog = $slwfile;
+request_slowlog_timeout = 1
+ping.path = /ping
+ping.response = pong
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ try {
+ run_request('127.0.0.1', $port);
+ echo "Ping ok\n";
+ } catch (Exception $e) {
+ echo "Ping error\n";
+ }
+ printf("File %s %s\n", $accfile, (file_exists(__DIR__.'/'.$accfile) ? "exists" : "missing"));
+ printf("File %s %s\n", $slwfile, (file_exists(__DIR__.'/'.$slwfile) ? "exists" : "missing"));
+
+ proc_terminate($fpm);
+ echo stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+ readfile(__DIR__.'/'.$accfile);
+}
+
+?>
+--EXPECTF--
+[%s] NOTICE: fpm is running, pid %d
+[%s] NOTICE: ready to handle connections
+Ping ok
+File php-fpm.acc.tmp exists
+File php-fpm.slw.tmp exists
+[%s] NOTICE: Terminating ...
+[%s] NOTICE: exiting, bye-bye!
+127.0.0.1 - %s "GET /ping" 200
+--CLEAN--
+<?php
+ $logfile = __DIR__.'/php-fpm.log.tmp';
+ $accfile = __DIR__.'/php-fpm.acc.tmp';
+ $slwfile = __DIR__.'/php-fpm.slw.tmp';
+ $pidfile = __DIR__.'/php-fpm.pid.tmp';
+ @unlink($logfile);
+ @unlink($accfile);
+ @unlink($slwfile);
+ @unlink($pidfile);
+?>
diff --git a/sapi/fpm/tests/021-uds-acl.phpt b/sapi/fpm/tests/021-uds-acl.phpt
new file mode 100644
index 000000000..f39c52641
--- /dev/null
+++ b/sapi/fpm/tests/021-uds-acl.phpt
@@ -0,0 +1,89 @@
+--TEST--
+FPM: Test Unix Domain Socket with Posix ACL
+--SKIPIF--
+<?php
+include "skipif.inc";
+if (!(file_exists('/usr/bin/getfacl') && file_exists('/etc/passwd') && file_exists('/etc/group'))) die ("skip missing getfacl command");
+?>
+--XFAIL--
+Mark as XFAIL because --with-fpm-acl is not enabled in default build
+--FILE--
+<?php
+
+include "include.inc";
+
+$logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+$socket = dirname(__FILE__).'/php-fpm.sock';
+
+// Select 3 users and 2 groups known by system (avoid root)
+$users = $groups = [];
+$tmp = file('/etc/passwd');
+for ($i=1 ; $i<=3 ; $i++) {
+ $tab = explode(':', $tmp[$i]);
+ $users[] = $tab[0];
+}
+$users = implode(',', $users);
+$tmp = file('/etc/group');
+for ($i=1 ; $i<=2 ; $i++) {
+ $tab = explode(':', $tmp[$i]);
+ $groups[] = $tab[0];
+}
+$groups = implode(',', $groups);
+
+$cfg = <<<EOT
+[global]
+error_log = $logfile
+[unconfined]
+listen = $socket
+listen.acl_users = $users
+listen.acl_groups = $groups
+listen.mode = 0600
+ping.path = /ping
+ping.response = pong
+pm = dynamic
+pm.max_children = 5
+pm.start_servers = 2
+pm.min_spare_servers = 1
+pm.max_spare_servers = 3
+EOT;
+
+$fpm = run_fpm($cfg, $tail);
+if (is_resource($fpm)) {
+ fpm_display_log($tail, 2);
+ try {
+ var_dump(strpos(run_request('unix://'.$socket, -1), 'pong'));
+ echo "UDS ok\n";
+ } catch (Exception $e) {
+ echo "UDS error\n";
+ }
+ passthru("/usr/bin/getfacl -cp $socket");
+
+ proc_terminate($fpm);
+ echo stream_get_contents($tail);
+ fclose($tail);
+ proc_close($fpm);
+}
+
+?>
+--EXPECTF--
+[%s] NOTICE: fpm is running, pid %d
+[%s] NOTICE: ready to handle connections
+int(%d)
+UDS ok
+user::rw-
+user:%s:rw-
+user:%s:rw-
+user:%s:rw-
+group::---
+group:%s:rw-
+group:%s:rw-
+mask::rw-
+other::---
+
+[%s] NOTICE: Terminating ...
+[%s] NOTICE: exiting, bye-bye!
+--CLEAN--
+<?php
+ $logfile = dirname(__FILE__).'/php-fpm.log.tmp';
+ @unlink($logfile);
+?>
diff --git a/sapi/fpm/tests/fcgi.inc b/sapi/fpm/tests/fcgi.inc
new file mode 100644
index 000000000..b31676260
--- /dev/null
+++ b/sapi/fpm/tests/fcgi.inc
@@ -0,0 +1,592 @@
+<?php
+/*
+ * This file is part of PHP-FastCGI-Client.
+ *
+ * (c) Pierrick Charron <pierrick@adoy.net>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+namespace Adoy\FastCGI;
+
+class TimedOutException extends \Exception {}
+class ForbiddenException extends \Exception {}
+
+/**
+ * Handles communication with a FastCGI application
+ *
+ * @author Pierrick Charron <pierrick@adoy.net>
+ * @version 1.0
+ */
+class Client
+{
+ const VERSION_1 = 1;
+
+ const BEGIN_REQUEST = 1;
+ const ABORT_REQUEST = 2;
+ const END_REQUEST = 3;
+ const PARAMS = 4;
+ const STDIN = 5;
+ const STDOUT = 6;
+ const STDERR = 7;
+ const DATA = 8;
+ const GET_VALUES = 9;
+ const GET_VALUES_RESULT = 10;
+ const UNKNOWN_TYPE = 11;
+ const MAXTYPE = self::UNKNOWN_TYPE;
+
+ const RESPONDER = 1;
+ const AUTHORIZER = 2;
+ const FILTER = 3;
+
+ const REQUEST_COMPLETE = 0;
+ const CANT_MPX_CONN = 1;
+ const OVERLOADED = 2;
+ const UNKNOWN_ROLE = 3;
+
+ const MAX_CONNS = 'MAX_CONNS';
+ const MAX_REQS = 'MAX_REQS';
+ const MPXS_CONNS = 'MPXS_CONNS';
+
+ const HEADER_LEN = 8;
+
+ const REQ_STATE_WRITTEN = 1;
+ const REQ_STATE_OK = 2;
+ const REQ_STATE_ERR = 3;
+ const REQ_STATE_TIMED_OUT = 4;
+
+ /**
+ * Socket
+ * @var Resource
+ */
+ private $_sock = null;
+
+ /**
+ * Host
+ * @var String
+ */
+ private $_host = null;
+
+ /**
+ * Port
+ * @var Integer
+ */
+ private $_port = null;
+
+ /**
+ * Keep Alive
+ * @var Boolean
+ */
+ private $_keepAlive = false;
+
+ /**
+ * Outstanding request statuses keyed by request id
+ *
+ * Each request is an array with following form:
+ *
+ * array(
+ * 'state' => REQ_STATE_*
+ * 'response' => null | string
+ * )
+ *
+ * @var array
+ */
+ private $_requests = array();
+
+ /**
+ * Use persistent sockets to connect to backend
+ * @var Boolean
+ */
+ private $_persistentSocket = false;
+
+ /**
+ * Connect timeout in milliseconds
+ * @var Integer
+ */
+ private $_connectTimeout = 5000;
+
+ /**
+ * Read/Write timeout in milliseconds
+ * @var Integer
+ */
+ private $_readWriteTimeout = 5000;
+
+ /**
+ * Constructor
+ *
+ * @param String $host Host of the FastCGI application
+ * @param Integer $port Port of the FastCGI application
+ */
+ public function __construct($host, $port)
+ {
+ $this->_host = $host;
+ $this->_port = $port;
+ }
+
+ /**
+ * Define whether or not the FastCGI application should keep the connection
+ * alive at the end of a request
+ *
+ * @param Boolean $b true if the connection should stay alive, false otherwise
+ */
+ public function setKeepAlive($b)
+ {
+ $this->_keepAlive = (boolean)$b;
+ if (!$this->_keepAlive && $this->_sock) {
+ fclose($this->_sock);
+ }
+ }
+
+ /**
+ * Get the keep alive status
+ *
+ * @return Boolean true if the connection should stay alive, false otherwise
+ */
+ public function getKeepAlive()
+ {
+ return $this->_keepAlive;
+ }
+
+ /**
+ * Define whether or not PHP should attempt to re-use sockets opened by previous
+ * request for efficiency
+ *
+ * @param Boolean $b true if persistent socket should be used, false otherwise
+ */
+ public function setPersistentSocket($b)
+ {
+ $was_persistent = ($this->_sock && $this->_persistentSocket);
+ $this->_persistentSocket = (boolean)$b;
+ if (!$this->_persistentSocket && $was_persistent) {
+ fclose($this->_sock);
+ }
+ }
+
+ /**
+ * Get the pesistent socket status
+ *
+ * @return Boolean true if the socket should be persistent, false otherwise
+ */
+ public function getPersistentSocket()
+ {
+ return $this->_persistentSocket;
+ }
+
+
+ /**
+ * Set the connect timeout
+ *
+ * @param Integer number of milliseconds before connect will timeout
+ */
+ public function setConnectTimeout($timeoutMs)
+ {
+ $this->_connectTimeout = $timeoutMs;
+ }
+
+ /**
+ * Get the connect timeout
+ *
+ * @return Integer number of milliseconds before connect will timeout
+ */
+ public function getConnectTimeout()
+ {
+ return $this->_connectTimeout;
+ }
+
+ /**
+ * Set the read/write timeout
+ *
+ * @param Integer number of milliseconds before read or write call will timeout
+ */
+ public function setReadWriteTimeout($timeoutMs)
+ {
+ $this->_readWriteTimeout = $timeoutMs;
+ $this->set_ms_timeout($this->_readWriteTimeout);
+ }
+
+ /**
+ * Get the read timeout
+ *
+ * @return Integer number of milliseconds before read will timeout
+ */
+ public function getReadWriteTimeout()
+ {
+ return $this->_readWriteTimeout;
+ }
+
+ /**
+ * Helper to avoid duplicating milliseconds to secs/usecs in a few places
+ *
+ * @param Integer millisecond timeout
+ * @return Boolean
+ */
+ private function set_ms_timeout($timeoutMs) {
+ if (!$this->_sock) {
+ return false;
+ }
+ return stream_set_timeout($this->_sock, floor($timeoutMs / 1000), ($timeoutMs % 1000) * 1000);
+ }
+
+
+ /**
+ * Create a connection to the FastCGI application
+ */
+ private function connect()
+ {
+ if (!$this->_sock) {
+ if ($this->_persistentSocket) {
+ $this->_sock = pfsockopen($this->_host, $this->_port, $errno, $errstr, $this->_connectTimeout/1000);
+ } else {
+ $this->_sock = fsockopen($this->_host, $this->_port, $errno, $errstr, $this->_connectTimeout/1000);
+ }
+
+ if (!$this->_sock) {
+ throw new \Exception('Unable to connect to FastCGI application: ' . $errstr);
+ }
+
+ if (!$this->set_ms_timeout($this->_readWriteTimeout)) {
+ throw new \Exception('Unable to set timeout on socket');
+ }
+ }
+ }
+
+ /**
+ * Build a FastCGI packet
+ *
+ * @param Integer $type Type of the packet
+ * @param String $content Content of the packet
+ * @param Integer $requestId RequestId
+ */
+ private function buildPacket($type, $content, $requestId = 1)
+ {
+ $clen = strlen($content);
+ return chr(self::VERSION_1) /* version */
+ . chr($type) /* type */
+ . chr(($requestId >> 8) & 0xFF) /* requestIdB1 */
+ . chr($requestId & 0xFF) /* requestIdB0 */
+ . chr(($clen >> 8 ) & 0xFF) /* contentLengthB1 */
+ . chr($clen & 0xFF) /* contentLengthB0 */
+ . chr(0) /* paddingLength */
+ . chr(0) /* reserved */
+ . $content; /* content */
+ }
+
+ /**
+ * Build an FastCGI Name value pair
+ *
+ * @param String $name Name
+ * @param String $value Value
+ * @return String FastCGI Name value pair
+ */
+ private function buildNvpair($name, $value)
+ {
+ $nlen = strlen($name);
+ $vlen = strlen($value);
+ if ($nlen < 128) {
+ /* nameLengthB0 */
+ $nvpair = chr($nlen);
+ } else {
+ /* nameLengthB3 & nameLengthB2 & nameLengthB1 & nameLengthB0 */
+ $nvpair = chr(($nlen >> 24) | 0x80) . chr(($nlen >> 16) & 0xFF) . chr(($nlen >> 8) & 0xFF) . chr($nlen & 0xFF);
+ }
+ if ($vlen < 128) {
+ /* valueLengthB0 */
+ $nvpair .= chr($vlen);
+ } else {
+ /* valueLengthB3 & valueLengthB2 & valueLengthB1 & valueLengthB0 */
+ $nvpair .= chr(($vlen >> 24) | 0x80) . chr(($vlen >> 16) & 0xFF) . chr(($vlen >> 8) & 0xFF) . chr($vlen & 0xFF);
+ }
+ /* nameData & valueData */
+ return $nvpair . $name . $value;
+ }
+
+ /**
+ * Read a set of FastCGI Name value pairs
+ *
+ * @param String $data Data containing the set of FastCGI NVPair
+ * @return array of NVPair
+ */
+ private function readNvpair($data, $length = null)
+ {
+ $array = array();
+
+ if ($length === null) {
+ $length = strlen($data);
+ }
+
+ $p = 0;
+
+ while ($p != $length) {
+
+ $nlen = ord($data{$p++});
+ if ($nlen >= 128) {
+ $nlen = ($nlen & 0x7F << 24);
+ $nlen |= (ord($data{$p++}) << 16);
+ $nlen |= (ord($data{$p++}) << 8);
+ $nlen |= (ord($data{$p++}));
+ }
+ $vlen = ord($data{$p++});
+ if ($vlen >= 128) {
+ $vlen = ($nlen & 0x7F << 24);
+ $vlen |= (ord($data{$p++}) << 16);
+ $vlen |= (ord($data{$p++}) << 8);
+ $vlen |= (ord($data{$p++}));
+ }
+ $array[substr($data, $p, $nlen)] = substr($data, $p+$nlen, $vlen);
+ $p += ($nlen + $vlen);
+ }
+
+ return $array;
+ }
+
+ /**
+ * Decode a FastCGI Packet
+ *
+ * @param String $data String containing all the packet
+ * @return array
+ */
+ private function decodePacketHeader($data)
+ {
+ $ret = array();
+ $ret['version'] = ord($data{0});
+ $ret['type'] = ord($data{1});
+ $ret['requestId'] = (ord($data{2}) << 8) + ord($data{3});
+ $ret['contentLength'] = (ord($data{4}) << 8) + ord($data{5});
+ $ret['paddingLength'] = ord($data{6});
+ $ret['reserved'] = ord($data{7});
+ return $ret;
+ }
+
+ /**
+ * Read a FastCGI Packet
+ *
+ * @return array
+ */
+ private function readPacket()
+ {
+ if ($packet = fread($this->_sock, self::HEADER_LEN)) {
+ $resp = $this->decodePacketHeader($packet);
+ $resp['content'] = '';
+ if ($resp['contentLength']) {
+ $len = $resp['contentLength'];
+ while ($len && $buf=fread($this->_sock, $len)) {
+ $len -= strlen($buf);
+ $resp['content'] .= $buf;
+ }
+ }
+ if ($resp['paddingLength']) {
+ $buf = fread($this->_sock, $resp['paddingLength']);
+ }
+ return $resp;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Get Informations on the FastCGI application
+ *
+ * @param array $requestedInfo information to retrieve
+ * @return array
+ */
+ public function getValues(array $requestedInfo)
+ {
+ $this->connect();
+
+ $request = '';
+ foreach ($requestedInfo as $info) {
+ $request .= $this->buildNvpair($info, '');
+ }
+ fwrite($this->_sock, $this->buildPacket(self::GET_VALUES, $request, 0));
+
+ $resp = $this->readPacket();
+ if ($resp['type'] == self::GET_VALUES_RESULT) {
+ return $this->readNvpair($resp['content'], $resp['length']);
+ } else {
+ throw new \Exception('Unexpected response type, expecting GET_VALUES_RESULT');
+ }
+ }
+
+ /**
+ * Execute a request to the FastCGI application
+ *
+ * @param array $params Array of parameters
+ * @param String $stdin Content
+ * @return String
+ */
+ public function request(array $params, $stdin)
+ {
+ $id = $this->async_request($params, $stdin);
+ return $this->wait_for_response($id);
+ }
+
+ /**
+ * Execute a request to the FastCGI application asyncronously
+ *
+ * This sends request to application and returns the assigned ID for that request.
+ *
+ * You should keep this id for later use with wait_for_response(). Ids are chosen randomly
+ * rather than seqentially to guard against false-positives when using persistent sockets.
+ * In that case it is possible that a delayed response to a request made by a previous script
+ * invocation comes back on this socket and is mistaken for response to request made with same ID
+ * during this request.
+ *
+ * @param array $params Array of parameters
+ * @param String $stdin Content
+ * @return Integer
+ */
+ public function async_request(array $params, $stdin)
+ {
+ $this->connect();
+
+ // Pick random number between 1 and max 16 bit unsigned int 65535
+ $id = mt_rand(1, (1 << 16) - 1);
+
+ // Using persistent sockets implies you want them keept alive by server!
+ $keepAlive = intval($this->_keepAlive || $this->_persistentSocket);
+
+ $request = $this->buildPacket(self::BEGIN_REQUEST
+ ,chr(0) . chr(self::RESPONDER) . chr($keepAlive) . str_repeat(chr(0), 5)
+ ,$id
+ );
+
+ $paramsRequest = '';
+ foreach ($params as $key => $value) {
+ $paramsRequest .= $this->buildNvpair($key, $value, $id);
+ }
+ if ($paramsRequest) {
+ $request .= $this->buildPacket(self::PARAMS, $paramsRequest, $id);
+ }
+ $request .= $this->buildPacket(self::PARAMS, '', $id);
+
+ if ($stdin) {
+ $request .= $this->buildPacket(self::STDIN, $stdin, $id);
+ }
+ $request .= $this->buildPacket(self::STDIN, '', $id);
+
+ if (fwrite($this->_sock, $request) === false || fflush($this->_sock) === false) {
+
+ $info = stream_get_meta_data($this->_sock);
+
+ if ($info['timed_out']) {
+ throw new TimedOutException('Write timed out');
+ }
+
+ // Broken pipe, tear down so future requests might succeed
+ fclose($this->_sock);
+ throw new \Exception('Failed to write request to socket');
+ }
+
+ $this->_requests[$id] = array(
+ 'state' => self::REQ_STATE_WRITTEN,
+ 'response' => null
+ );
+
+ return $id;
+ }
+
+ /**
+ * Blocking call that waits for response to specific request
+ *
+ * @param Integer $requestId
+ * @param Integer $timeoutMs [optional] the number of milliseconds to wait. Defaults to the ReadWriteTimeout value set.
+ * @return string response body
+ */
+ public function wait_for_response($requestId, $timeoutMs = 0) {
+
+ if (!isset($this->_requests[$requestId])) {
+ throw new \Exception('Invalid request id given');
+ }
+
+ // If we already read the response during an earlier call for different id, just return it
+ if ($this->_requests[$requestId]['state'] == self::REQ_STATE_OK
+ || $this->_requests[$requestId]['state'] == self::REQ_STATE_ERR
+ ) {
+ return $this->_requests[$requestId]['response'];
+ }
+
+ if ($timeoutMs > 0) {
+ // Reset timeout on socket for now
+ $this->set_ms_timeout($timeoutMs);
+ } else {
+ $timeoutMs = $this->_readWriteTimeout;
+ }
+
+ // Need to manually check since we might do several reads none of which timeout themselves
+ // but still not get the response requested
+ $startTime = microtime(true);
+
+ do {
+ $resp = $this->readPacket();
+
+ if ($resp['type'] == self::STDOUT || $resp['type'] == self::STDERR) {
+ if ($resp['type'] == self::STDERR) {
+ $this->_requests[$resp['requestId']]['state'] = self::REQ_STATE_ERR;
+ }
+ $this->_requests[$resp['requestId']]['response'] .= $resp['content'];
+ }
+ if ($resp['type'] == self::END_REQUEST) {
+ $this->_requests[$resp['requestId']]['state'] = self::REQ_STATE_OK;
+ if ($resp['requestId'] == $requestId) {
+ break;
+ }
+ }
+ if (microtime(true) - $startTime >= ($timeoutMs * 1000)) {
+ // Reset
+ $this->set_ms_timeout($this->_readWriteTimeout);
+ throw new \Exception('Timed out');
+ }
+ } while ($resp);
+
+ if (!is_array($resp)) {
+ $info = stream_get_meta_data($this->_sock);
+
+ // We must reset timeout but it must be AFTER we get info
+ $this->set_ms_timeout($this->_readWriteTimeout);
+
+ if ($info['timed_out']) {
+ throw new TimedOutException('Read timed out');
+ }
+
+ if ($info['unread_bytes'] == 0
+ && $info['blocked']
+ && $info['eof']) {
+ throw new ForbiddenException('Not in white list. Check listen.allowed_clients.');
+ }
+
+ throw new \Exception('Read failed');
+ }
+
+ // Reset timeout
+ $this->set_ms_timeout($this->_readWriteTimeout);
+
+ switch (ord($resp['content']{4})) {
+ case self::CANT_MPX_CONN:
+ throw new \Exception('This app can\'t multiplex [CANT_MPX_CONN]');
+ break;
+ case self::OVERLOADED:
+ throw new \Exception('New request rejected; too busy [OVERLOADED]');
+ break;
+ case self::UNKNOWN_ROLE:
+ throw new \Exception('Role value not known [UNKNOWN_ROLE]');
+ break;
+ case self::REQUEST_COMPLETE:
+ return $this->_requests[$requestId]['response'];
+ }
+ }
+}
diff --git a/sapi/fpm/tests/include.inc b/sapi/fpm/tests/include.inc
index 983cbd345..b195fad50 100644
--- a/sapi/fpm/tests/include.inc
+++ b/sapi/fpm/tests/include.inc
@@ -76,4 +76,36 @@ function run_fpm_till($needle, $config, $max = 10) /* {{{ */
}
/* }}} */
-?>
+function fpm_display_log($tail, $n=1, $ignore='systemd') {
+ while ($n) {
+ $a = fgets($tail);
+ if (empty($ignore) || !strpos($a, $ignore)) {
+ echo $a;
+ $n--;
+ }
+ }
+}
+
+function run_request($host, $port, $uri='/ping', $query='') {
+ require_once 'fcgi.inc';
+ $client = new Adoy\FastCGI\Client($host, $port);
+ $params = array(
+ 'GATEWAY_INTERFACE' => 'FastCGI/1.0',
+ 'REQUEST_METHOD' => 'GET',
+ 'SCRIPT_FILENAME' => $uri,
+ 'SCRIPT_NAME' => $uri,
+ 'QUERY_STRING' => $query,
+ 'REQUEST_URI' => $uri . ($query ? '?'.$query : ""),
+ 'DOCUMENT_URI' => $uri,
+ 'SERVER_SOFTWARE' => 'php/fcgiclient',
+ 'REMOTE_ADDR' => '127.0.0.1',
+ 'REMOTE_PORT' => '9985',
+ 'SERVER_ADDR' => '127.0.0.1',
+ 'SERVER_PORT' => '80',
+ 'SERVER_NAME' => php_uname('n'),
+ 'SERVER_PROTOCOL' => 'HTTP/1.1',
+ 'CONTENT_TYPE' => '',
+ 'CONTENT_LENGTH' => 0
+ );
+ return $client->request($params, false)."\n";
+}