mirror of
https://github.com/php/php-src.git
synced 2025-08-16 14:08:47 +02:00
Fix bug #68591: Configuration test does not perform UID lookups
Addition check in fpm config test to verify existence of user, group, listen.owner and listen.group. Closes GH-10165
This commit is contained in:
parent
0464524292
commit
21d8980d27
9 changed files with 242 additions and 25 deletions
2
NEWS
2
NEWS
|
@ -6,6 +6,8 @@ PHP NEWS
|
||||||
. Fixed bug #77106 (Missing separator in FPM FastCGI errors). (Jakub Zelenka)
|
. Fixed bug #77106 (Missing separator in FPM FastCGI errors). (Jakub Zelenka)
|
||||||
. Fixed bug GH-9981 (FPM does not reset fastcgi.error_header).
|
. Fixed bug GH-9981 (FPM does not reset fastcgi.error_header).
|
||||||
(Jakub Zelenka)
|
(Jakub Zelenka)
|
||||||
|
. Fixed bug #68591 (Configuration test does not perform UID lookups).
|
||||||
|
(Jakub Zelenka)
|
||||||
|
|
||||||
- LDAP:
|
- LDAP:
|
||||||
. Fixed bug GH-10112 (LDAP\Connection::__construct() refers to ldap_create()).
|
. Fixed bug GH-10112 (LDAP\Connection::__construct() refers to ldap_create()).
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
#include "fpm_status.h"
|
#include "fpm_status.h"
|
||||||
#include "fpm_log.h"
|
#include "fpm_log.h"
|
||||||
#include "fpm_events.h"
|
#include "fpm_events.h"
|
||||||
|
#include "fpm_unix.h"
|
||||||
#include "zlog.h"
|
#include "zlog.h"
|
||||||
#ifdef HAVE_SYSTEMD
|
#ifdef HAVE_SYSTEMD
|
||||||
#include "fpm_systemd.h"
|
#include "fpm_systemd.h"
|
||||||
|
@ -1854,6 +1855,12 @@ int fpm_conf_init_main(int test_conf, int force_daemon) /* {{{ */
|
||||||
}
|
}
|
||||||
|
|
||||||
if (test_conf) {
|
if (test_conf) {
|
||||||
|
for (struct fpm_worker_pool_s *wp = fpm_worker_all_pools; wp; wp = wp->next) {
|
||||||
|
if (!fpm_unix_test_config(wp)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (test_conf > 1) {
|
if (test_conf > 1) {
|
||||||
fpm_conf_dump();
|
fpm_conf_dump();
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,55 @@
|
||||||
|
|
||||||
size_t fpm_pagesize;
|
size_t fpm_pagesize;
|
||||||
|
|
||||||
|
|
||||||
|
static inline bool fpm_unix_is_id(const char* name)
|
||||||
|
{
|
||||||
|
return strlen(name) == strspn(name, "0123456789");
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct passwd *fpm_unix_get_passwd(struct fpm_worker_pool_s *wp, const char *name, int flags)
|
||||||
|
{
|
||||||
|
struct passwd *pwd = getpwnam(name);
|
||||||
|
if (!pwd) {
|
||||||
|
zlog(flags, "[pool %s] cannot get uid for user '%s'", wp->config->name, name);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool fpm_unix_check_passwd(struct fpm_worker_pool_s *wp, const char *name, int flags)
|
||||||
|
{
|
||||||
|
return !name || fpm_unix_is_id(name) || fpm_unix_get_passwd(wp, name, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct group *fpm_unix_get_group(struct fpm_worker_pool_s *wp, const char *name, int flags)
|
||||||
|
{
|
||||||
|
struct group *group = getgrnam(name);
|
||||||
|
if (!group) {
|
||||||
|
zlog(flags, "[pool %s] cannot get gid for group '%s'", wp->config->name, name);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool fpm_unix_check_group(struct fpm_worker_pool_s *wp, const char *name, int flags)
|
||||||
|
{
|
||||||
|
return !name || fpm_unix_is_id(name) || fpm_unix_get_group(wp, name, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fpm_unix_test_config(struct fpm_worker_pool_s *wp)
|
||||||
|
{
|
||||||
|
struct fpm_worker_pool_config_s *config = wp->config;
|
||||||
|
return (
|
||||||
|
fpm_unix_check_passwd(wp, config->user, ZLOG_ERROR) &&
|
||||||
|
fpm_unix_check_group(wp, config->group, ZLOG_ERROR) &&
|
||||||
|
fpm_unix_check_passwd(wp, config->listen_owner, ZLOG_SYSERROR) &&
|
||||||
|
fpm_unix_check_group(wp, config->listen_group, ZLOG_SYSERROR)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
int fpm_unix_resolve_socket_permissions(struct fpm_worker_pool_s *wp) /* {{{ */
|
int fpm_unix_resolve_socket_permissions(struct fpm_worker_pool_s *wp) /* {{{ */
|
||||||
{
|
{
|
||||||
struct fpm_worker_pool_config_s *c = wp->config;
|
struct fpm_worker_pool_config_s *c = wp->config;
|
||||||
|
@ -105,11 +154,10 @@ int fpm_unix_resolve_socket_permissions(struct fpm_worker_pool_s *wp) /* {{{ */
|
||||||
if ((end = strchr(p, ','))) {
|
if ((end = strchr(p, ','))) {
|
||||||
*end++ = 0;
|
*end++ = 0;
|
||||||
}
|
}
|
||||||
pwd = getpwnam(p);
|
pwd = fpm_unix_get_passwd(wp, p, ZLOG_SYSERROR);
|
||||||
if (pwd) {
|
if (pwd) {
|
||||||
zlog(ZLOG_DEBUG, "[pool %s] user '%s' have uid=%d", wp->config->name, p, pwd->pw_uid);
|
zlog(ZLOG_DEBUG, "[pool %s] user '%s' have uid=%d", wp->config->name, p, pwd->pw_uid);
|
||||||
} else {
|
} else {
|
||||||
zlog(ZLOG_SYSERROR, "[pool %s] cannot get uid for user '%s'", wp->config->name, p);
|
|
||||||
acl_free(acl);
|
acl_free(acl);
|
||||||
efree(tmp);
|
efree(tmp);
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -138,11 +186,10 @@ int fpm_unix_resolve_socket_permissions(struct fpm_worker_pool_s *wp) /* {{{ */
|
||||||
if ((end = strchr(p, ','))) {
|
if ((end = strchr(p, ','))) {
|
||||||
*end++ = 0;
|
*end++ = 0;
|
||||||
}
|
}
|
||||||
grp = getgrnam(p);
|
grp = fpm_unix_get_group(wp, p, ZLOG_SYSERROR);
|
||||||
if (grp) {
|
if (grp) {
|
||||||
zlog(ZLOG_DEBUG, "[pool %s] group '%s' have gid=%d", wp->config->name, p, grp->gr_gid);
|
zlog(ZLOG_DEBUG, "[pool %s] group '%s' have gid=%d", wp->config->name, p, grp->gr_gid);
|
||||||
} else {
|
} else {
|
||||||
zlog(ZLOG_SYSERROR, "[pool %s] cannot get gid for group '%s'", wp->config->name, p);
|
|
||||||
acl_free(acl);
|
acl_free(acl);
|
||||||
efree(tmp);
|
efree(tmp);
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -175,14 +222,13 @@ int fpm_unix_resolve_socket_permissions(struct fpm_worker_pool_s *wp) /* {{{ */
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (c->listen_owner && *c->listen_owner) {
|
if (c->listen_owner && *c->listen_owner) {
|
||||||
if (strlen(c->listen_owner) == strspn(c->listen_owner, "0123456789")) {
|
if (fpm_unix_is_id(c->listen_owner)) {
|
||||||
wp->socket_uid = strtoul(c->listen_owner, 0, 10);
|
wp->socket_uid = strtoul(c->listen_owner, 0, 10);
|
||||||
} else {
|
} else {
|
||||||
struct passwd *pwd;
|
struct passwd *pwd;
|
||||||
|
|
||||||
pwd = getpwnam(c->listen_owner);
|
pwd = fpm_unix_get_passwd(wp, c->listen_owner, ZLOG_SYSERROR);
|
||||||
if (!pwd) {
|
if (!pwd) {
|
||||||
zlog(ZLOG_SYSERROR, "[pool %s] cannot get uid for user '%s'", wp->config->name, c->listen_owner);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,14 +238,13 @@ int fpm_unix_resolve_socket_permissions(struct fpm_worker_pool_s *wp) /* {{{ */
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c->listen_group && *c->listen_group) {
|
if (c->listen_group && *c->listen_group) {
|
||||||
if (strlen(c->listen_group) == strspn(c->listen_group, "0123456789")) {
|
if (fpm_unix_is_id(c->listen_group)) {
|
||||||
wp->socket_gid = strtoul(c->listen_group, 0, 10);
|
wp->socket_gid = strtoul(c->listen_group, 0, 10);
|
||||||
} else {
|
} else {
|
||||||
struct group *grp;
|
struct group *grp;
|
||||||
|
|
||||||
grp = getgrnam(c->listen_group);
|
grp = fpm_unix_get_group(wp, c->listen_group, ZLOG_SYSERROR);
|
||||||
if (!grp) {
|
if (!grp) {
|
||||||
zlog(ZLOG_SYSERROR, "[pool %s] cannot get gid for group '%s'", wp->config->name, c->listen_group);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
wp->socket_gid = grp->gr_gid;
|
wp->socket_gid = grp->gr_gid;
|
||||||
|
@ -279,7 +324,7 @@ static int fpm_unix_conf_wp(struct fpm_worker_pool_s *wp) /* {{{ */
|
||||||
|
|
||||||
if (is_root) {
|
if (is_root) {
|
||||||
if (wp->config->user && *wp->config->user) {
|
if (wp->config->user && *wp->config->user) {
|
||||||
if (strlen(wp->config->user) == strspn(wp->config->user, "0123456789")) {
|
if (fpm_unix_is_id(wp->config->user)) {
|
||||||
wp->set_uid = strtoul(wp->config->user, 0, 10);
|
wp->set_uid = strtoul(wp->config->user, 0, 10);
|
||||||
pwd = getpwuid(wp->set_uid);
|
pwd = getpwuid(wp->set_uid);
|
||||||
if (pwd) {
|
if (pwd) {
|
||||||
|
@ -289,9 +334,8 @@ static int fpm_unix_conf_wp(struct fpm_worker_pool_s *wp) /* {{{ */
|
||||||
} else {
|
} else {
|
||||||
struct passwd *pwd;
|
struct passwd *pwd;
|
||||||
|
|
||||||
pwd = getpwnam(wp->config->user);
|
pwd = fpm_unix_get_passwd(wp, wp->config->user, ZLOG_ERROR);
|
||||||
if (!pwd) {
|
if (!pwd) {
|
||||||
zlog(ZLOG_ERROR, "[pool %s] cannot get uid for user '%s'", wp->config->name, wp->config->user);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,14 +348,13 @@ static int fpm_unix_conf_wp(struct fpm_worker_pool_s *wp) /* {{{ */
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wp->config->group && *wp->config->group) {
|
if (wp->config->group && *wp->config->group) {
|
||||||
if (strlen(wp->config->group) == strspn(wp->config->group, "0123456789")) {
|
if (fpm_unix_is_id(wp->config->group)) {
|
||||||
wp->set_gid = strtoul(wp->config->group, 0, 10);
|
wp->set_gid = strtoul(wp->config->group, 0, 10);
|
||||||
} else {
|
} else {
|
||||||
struct group *grp;
|
struct group *grp;
|
||||||
|
|
||||||
grp = getgrnam(wp->config->group);
|
grp = fpm_unix_get_group(wp, wp->config->group, ZLOG_ERROR);
|
||||||
if (!grp) {
|
if (!grp) {
|
||||||
zlog(ZLOG_ERROR, "[pool %s] cannot get gid for group '%s'", wp->config->name, wp->config->group);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
wp->set_gid = grp->gr_gid;
|
wp->set_gid = grp->gr_gid;
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
|
|
||||||
#include "fpm_worker_pool.h"
|
#include "fpm_worker_pool.h"
|
||||||
|
|
||||||
|
bool fpm_unix_test_config(struct fpm_worker_pool_s *wp);
|
||||||
|
|
||||||
int fpm_unix_resolve_socket_permissions(struct fpm_worker_pool_s *wp);
|
int fpm_unix_resolve_socket_permissions(struct fpm_worker_pool_s *wp);
|
||||||
int fpm_unix_set_socket_permissions(struct fpm_worker_pool_s *wp, const char *path);
|
int fpm_unix_set_socket_permissions(struct fpm_worker_pool_s *wp, const char *path);
|
||||||
int fpm_unix_free_socket_permissions(struct fpm_worker_pool_s *wp);
|
int fpm_unix_free_socket_permissions(struct fpm_worker_pool_s *wp);
|
||||||
|
|
38
sapi/fpm/tests/bug68591-conf-test-group.phpt
Normal file
38
sapi/fpm/tests/bug68591-conf-test-group.phpt
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
--TEST--
|
||||||
|
FPM: bug68591 - config test group existence
|
||||||
|
--SKIPIF--
|
||||||
|
<?php
|
||||||
|
include "skipif.inc";
|
||||||
|
?>
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once "tester.inc";
|
||||||
|
|
||||||
|
$cfg = <<<EOT
|
||||||
|
[global]
|
||||||
|
error_log = {{FILE:LOG}}
|
||||||
|
[unconfined]
|
||||||
|
listen = {{ADDR:UDS}}
|
||||||
|
group = aaaaaa
|
||||||
|
pm = dynamic
|
||||||
|
pm.max_children = 5
|
||||||
|
pm.start_servers = 2
|
||||||
|
pm.min_spare_servers = 1
|
||||||
|
pm.max_spare_servers = 3
|
||||||
|
EOT;
|
||||||
|
|
||||||
|
$tester = new FPM\Tester($cfg);
|
||||||
|
$tester->testConfig();
|
||||||
|
|
||||||
|
?>
|
||||||
|
Done
|
||||||
|
--EXPECT--
|
||||||
|
ERROR: [pool unconfined] cannot get gid for group 'aaaaaa'
|
||||||
|
ERROR: FPM initialization failed
|
||||||
|
Done
|
||||||
|
--CLEAN--
|
||||||
|
<?php
|
||||||
|
require_once "tester.inc";
|
||||||
|
FPM\Tester::clean();
|
||||||
|
?>
|
38
sapi/fpm/tests/bug68591-conf-test-listen-group.phpt
Normal file
38
sapi/fpm/tests/bug68591-conf-test-listen-group.phpt
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
--TEST--
|
||||||
|
FPM: bug68591 - config test listen group existence
|
||||||
|
--SKIPIF--
|
||||||
|
<?php
|
||||||
|
include "skipif.inc";
|
||||||
|
?>
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once "tester.inc";
|
||||||
|
|
||||||
|
$cfg = <<<EOT
|
||||||
|
[global]
|
||||||
|
error_log = {{FILE:LOG}}
|
||||||
|
[unconfined]
|
||||||
|
listen = {{ADDR:UDS}}
|
||||||
|
listen.group = aaaaaa
|
||||||
|
pm = dynamic
|
||||||
|
pm.max_children = 5
|
||||||
|
pm.start_servers = 2
|
||||||
|
pm.min_spare_servers = 1
|
||||||
|
pm.max_spare_servers = 3
|
||||||
|
EOT;
|
||||||
|
|
||||||
|
$tester = new FPM\Tester($cfg);
|
||||||
|
$tester->testConfig();
|
||||||
|
|
||||||
|
?>
|
||||||
|
Done
|
||||||
|
--EXPECTF--
|
||||||
|
ERROR: [pool unconfined] cannot get gid for group 'aaaaaa': %s
|
||||||
|
ERROR: FPM initialization failed
|
||||||
|
Done
|
||||||
|
--CLEAN--
|
||||||
|
<?php
|
||||||
|
require_once "tester.inc";
|
||||||
|
FPM\Tester::clean();
|
||||||
|
?>
|
38
sapi/fpm/tests/bug68591-conf-test-listen-owner.phpt
Normal file
38
sapi/fpm/tests/bug68591-conf-test-listen-owner.phpt
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
--TEST--
|
||||||
|
FPM: bug68591 - config test listen owner existence
|
||||||
|
--SKIPIF--
|
||||||
|
<?php
|
||||||
|
include "skipif.inc";
|
||||||
|
?>
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once "tester.inc";
|
||||||
|
|
||||||
|
$cfg = <<<EOT
|
||||||
|
[global]
|
||||||
|
error_log = {{FILE:LOG}}
|
||||||
|
[unconfined]
|
||||||
|
listen = {{ADDR:UDS}}
|
||||||
|
listen.owner = aaaaaa
|
||||||
|
pm = dynamic
|
||||||
|
pm.max_children = 5
|
||||||
|
pm.start_servers = 2
|
||||||
|
pm.min_spare_servers = 1
|
||||||
|
pm.max_spare_servers = 3
|
||||||
|
EOT;
|
||||||
|
|
||||||
|
$tester = new FPM\Tester($cfg);
|
||||||
|
$tester->testConfig();
|
||||||
|
|
||||||
|
?>
|
||||||
|
Done
|
||||||
|
--EXPECTF--
|
||||||
|
ERROR: [pool unconfined] cannot get uid for user 'aaaaaa': %s
|
||||||
|
ERROR: FPM initialization failed
|
||||||
|
Done
|
||||||
|
--CLEAN--
|
||||||
|
<?php
|
||||||
|
require_once "tester.inc";
|
||||||
|
FPM\Tester::clean();
|
||||||
|
?>
|
38
sapi/fpm/tests/bug68591-conf-test-user.phpt
Normal file
38
sapi/fpm/tests/bug68591-conf-test-user.phpt
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
--TEST--
|
||||||
|
FPM: bug68591 - config test user existence
|
||||||
|
--SKIPIF--
|
||||||
|
<?php
|
||||||
|
include "skipif.inc";
|
||||||
|
?>
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once "tester.inc";
|
||||||
|
|
||||||
|
$cfg = <<<EOT
|
||||||
|
[global]
|
||||||
|
error_log = {{FILE:LOG}}
|
||||||
|
[unconfined]
|
||||||
|
listen = {{ADDR:UDS}}
|
||||||
|
user = aaaaaa
|
||||||
|
pm = dynamic
|
||||||
|
pm.max_children = 5
|
||||||
|
pm.start_servers = 2
|
||||||
|
pm.min_spare_servers = 1
|
||||||
|
pm.max_spare_servers = 3
|
||||||
|
EOT;
|
||||||
|
|
||||||
|
$tester = new FPM\Tester($cfg);
|
||||||
|
$tester->testConfig();
|
||||||
|
|
||||||
|
?>
|
||||||
|
Done
|
||||||
|
--EXPECT--
|
||||||
|
ERROR: [pool unconfined] cannot get uid for user 'aaaaaa'
|
||||||
|
ERROR: FPM initialization failed
|
||||||
|
Done
|
||||||
|
--CLEAN--
|
||||||
|
<?php
|
||||||
|
require_once "tester.inc";
|
||||||
|
FPM\Tester::clean();
|
||||||
|
?>
|
|
@ -269,10 +269,11 @@ class Tester
|
||||||
static public function skipIfConfigFails(string $configTemplate)
|
static public function skipIfConfigFails(string $configTemplate)
|
||||||
{
|
{
|
||||||
$tester = new self($configTemplate, '', [], self::getCallerFileName());
|
$tester = new self($configTemplate, '', [], self::getCallerFileName());
|
||||||
$testResult = $tester->testConfig();
|
$testResult = $tester->testConfig(true);
|
||||||
if ($testResult !== null) {
|
if ($testResult !== null) {
|
||||||
self::clean(2);
|
self::clean(2);
|
||||||
die("skip $testResult");
|
$message = $testResult[0] ?? 'Config failed';
|
||||||
|
die("skip $message");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,17 +379,26 @@ class Tester
|
||||||
/**
|
/**
|
||||||
* Test configuration file.
|
* Test configuration file.
|
||||||
*
|
*
|
||||||
* @return null|string
|
* @return null|array
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public function testConfig()
|
public function testConfig($silent = false)
|
||||||
{
|
{
|
||||||
$configFile = $this->createConfig();
|
$configFile = $this->createConfig();
|
||||||
$cmd = self::findExecutable() . ' -tt -y ' . $configFile . ' 2>&1';
|
$cmd = self::findExecutable() . ' -tt -y ' . $configFile . ' 2>&1';
|
||||||
$this->trace('Testing config using command', $cmd, true);
|
$this->trace('Testing config using command', $cmd, true);
|
||||||
exec($cmd, $output, $code);
|
exec($cmd, $output, $code);
|
||||||
if ($code) {
|
if ($code) {
|
||||||
return preg_replace("/\[.+?\]/", "", $output[0]);
|
$messages = [];
|
||||||
|
foreach ($output as $outputLine) {
|
||||||
|
$message = preg_replace("/\[.+?\]/", "", $outputLine, 1);
|
||||||
|
$messages[] = $message;
|
||||||
|
if ( ! $silent) {
|
||||||
|
$this->error($message, null, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -1259,14 +1269,15 @@ class Tester
|
||||||
/**
|
/**
|
||||||
* Display error.
|
* Display error.
|
||||||
*
|
*
|
||||||
* @param string $msg
|
* @param string $msg Error message.
|
||||||
* @param \Exception|null $exception
|
* @param \Exception|null $exception If there is an exception, log its message
|
||||||
|
* @param bool $prefix Whether to prefix the error message
|
||||||
*
|
*
|
||||||
* @return false
|
* @return false
|
||||||
*/
|
*/
|
||||||
private function error($msg, \Exception $exception = null): bool
|
private function error(string $msg, \Exception $exception = null, bool $prefix = true): bool
|
||||||
{
|
{
|
||||||
$this->error = 'ERROR: ' . $msg;
|
$this->error = $prefix ? 'ERROR: ' . $msg : ltrim($msg);
|
||||||
if ($exception) {
|
if ($exception) {
|
||||||
$this->error .= '; EXCEPTION: ' . $exception->getMessage();
|
$this->error .= '; EXCEPTION: ' . $exception->getMessage();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue