diff options
| author | Igor Pashev <pashev.igor@gmail.com> | 2015-02-20 22:25:42 +0300 |
|---|---|---|
| committer | Igor Pashev <pashev.igor@gmail.com> | 2015-02-20 22:25:42 +0300 |
| commit | 71d41ca6bb3a9d888b39f34a30f994ac1cf88873 (patch) | |
| tree | 3a11365b77b332c078440204fb82549d34237a35 /sapi/fpm | |
| parent | 2d50280dd013556e51b6f275ca965fe7b530029b (diff) | |
| parent | 1ceec3a053647865493ab417d3ce401b9bc42450 (diff) | |
| download | php-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')
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"; +} |
