Compare commits

..

1 Commits

@ -29,6 +29,7 @@ jobs:
run: mkdir -p build/logs || true run: mkdir -p build/logs || true
- name: Coveralls - name: Coveralls
run: vendor/bin/coveralls ./clover.xml || true run: php vendor/bin/php-coveralls -v --coverage_clover=coverage.xml || true
env: env:
COVERALLS_RUN_LOCALLY: 1
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}

@ -7,13 +7,14 @@
REQUIREMENTS REQUIREMENTS
------------ ------------
- Postfix - Postfix 2.0 or higher.
- Apache / Lighttpd - Apache 1.3.27 / Lighttpd 1.3.15 or higher.
- PHP (for web server) - PHP 5.1.2 or higher.
- one of the following databases: - one of the following databases:
- MariaDB/MySQL - MySQL 3.23 or higher (5.x recommended)
- PostgreSQL - MariaDB (counts as MySQL ;-)
- SQLite - PostgreSQL 7.4 (or higher)
- SQLite 3.12 (or higher)
READ THIS FIRST! READ THIS FIRST!
@ -41,10 +42,10 @@ DOCUMENTS/ folder.
(if you installed PostfixAdmin as RPM or DEB package, you can obviously skip this step.) (if you installed PostfixAdmin as RPM or DEB package, you can obviously skip this step.)
Assuming we are installing Postfixadmin into /srv/postfixadmin, then something like this should work. Please check https://github.com/postfixadmin/postfixadmin/releases to get the latest stable release first (the 3.2.4 version/url below is probably stale) Assuming we are installing Postfixadmin into /srv/postfixadmin, then something like this should work :
$ cd /srv/ $ cd /srv/
$ wget -O postfixadmin.tgz https://github.com/postfixadmin/postfixadmin/archive/postfixadmin-3.2.4.tar.gz $ wget -O postfixadmin.tgz https://github.com/postfixadmin/postfixadmin/archive/postfixadmin-3.2.tar.gz
$ tar -zxvf postfixadmin.tgz $ tar -zxvf postfixadmin.tgz
$ mv postfixadmin-postfixadmin-3.2 postfixadmin $ mv postfixadmin-postfixadmin-3.2 postfixadmin
@ -53,7 +54,7 @@ Alternatively :
$ cd /srv $ cd /srv
$ git clone https://github.com/postfixadmin/postfixadmin.git $ git clone https://github.com/postfixadmin/postfixadmin.git
$ cd postfixadmin $ cd postfixadmin
$ git checkout postfixadmin-3.2.4 $ git checkout postfixadmin-3.2.2
2. Setup Web Server 2. Setup Web Server
------------------- -------------------
@ -84,7 +85,6 @@ For MySQL:
CREATE DATABASE postfix; CREATE DATABASE postfix;
CREATE USER 'postfix'@'localhost' IDENTIFIED BY 'choose_a_password'; CREATE USER 'postfix'@'localhost' IDENTIFIED BY 'choose_a_password';
GRANT ALL PRIVILEGES ON `postfix` . * TO 'postfix'@'localhost'; GRANT ALL PRIVILEGES ON `postfix` . * TO 'postfix'@'localhost';
FLUSH PRIVILEGES;
For PostgreSQL: For PostgreSQL:
CREATE USER postfix WITH PASSWORD 'whatever'; CREATE USER postfix WITH PASSWORD 'whatever';

@ -14,9 +14,9 @@
"check-format": "php-cs-fixer fix --ansi --dry-run --diff", "check-format": "php-cs-fixer fix --ansi --dry-run --diff",
"format": "php-cs-fixer fix --ansi", "format": "php-cs-fixer fix --ansi",
"lint": "@php ./vendor/bin/parallel-lint --exclude vendor/ --exclude lib/block_random_int.php --exclude lib/array_column.php .", "lint": "@php ./vendor/bin/parallel-lint --exclude vendor/ --exclude lib/block_random_int.php --exclude lib/array_column.php .",
"test": "DATABASE=sqlite ./vendor/bin/phpunit --coverage-clover ./clover.xml tests/", "test": "DATABASE=sqlite ./vendor/bin/phpunit --coverage-clover ./coverage.xml tests/",
"test-fixup": "mkdir -p templates_c ; test -f config.local.php || touch config.local.php", "test-fixup": "mkdir -p templates_c ; test -f config.local.php || touch config.local.php",
"psalm": "@php ./vendor/bin/psalm --show-info=false " "psalm": "@php ./vendor/bin/psalm --no-cache --show-info=false "
}, },
"require": { "require": {
"php": ">=7.0" "php": ">=7.0"
@ -25,10 +25,10 @@
"ext-mysqli": "*", "ext-mysqli": "*",
"ext-sqlite3": "*", "ext-sqlite3": "*",
"friendsofphp/php-cs-fixer": "*", "friendsofphp/php-cs-fixer": "*",
"php-parallel-lint/php-parallel-lint": "^1.0", "jakub-onderka/php-parallel-lint": "^1.0",
"php": ">7.2.0", "php": ">7.2.0",
"cedx/coveralls": "^11.0", "php-coveralls/php-coveralls" : "*",
"phpunit/phpunit": "8.*", "phpunit/phpunit": "^6|^7",
"vimeo/psalm":"^3.0", "vimeo/psalm":"^3.0",
"shardj/zf1-future" : "^1.12" "shardj/zf1-future" : "^1.12"
}, },
@ -38,10 +38,5 @@
"functions.inc.php", "functions.inc.php",
"lib/smarty/libs/bootstrap.php" "lib/smarty/libs/bootstrap.php"
] ]
},
"support": {
"irc": "irc://irc.freenode.org/postfixadmin",
"issues": "https://github.com/postfixadmin/postfixadmin/issues",
"chat": "https://gitter.im/postfixadmin/Lobby"
} }
} }

@ -184,7 +184,7 @@ $CONF['smtp_sendmail_tls'] = 'NO';
// mysql_encrypt = useful for PAM integration // mysql_encrypt = useful for PAM integration
// authlib = support for courier-authlib style passwords - also set $CONF['authlib_default_flavor'] // authlib = support for courier-authlib style passwords - also set $CONF['authlib_default_flavor']
// dovecot:CRYPT-METHOD = use dovecotpw -s 'CRYPT-METHOD'. Example: dovecot:CRAM-MD5 // dovecot:CRYPT-METHOD = use dovecotpw -s 'CRYPT-METHOD'. Example: dovecot:CRAM-MD5
// php_crypt:CRYPT-METHOD:DIFFICULTY = use PHP built in crypt()-function. Example: php_crypt:SHA512:50000 // php_crypt:CRYPT-METHOD:DIFFICULTY:PREFIX = use PHP built in crypt()-function. Example: php_crypt:SHA512:50000
// - php_crypt CRYPT-METHOD: Supported values are DES, MD5, BLOWFISH, SHA256, SHA512 // - php_crypt CRYPT-METHOD: Supported values are DES, MD5, BLOWFISH, SHA256, SHA512
// - php_crypt DIFFICULTY: Larger value is more secure, but uses more CPU and time for each login. // - php_crypt DIFFICULTY: Larger value is more secure, but uses more CPU and time for each login.
// - php_crypt DIFFICULTY: Set this according to your CPU processing power. // - php_crypt DIFFICULTY: Set this according to your CPU processing power.
@ -194,6 +194,7 @@ $CONF['smtp_sendmail_tls'] = 'NO';
// - don't use dovecot:* methods that include the username in the hash - you won't be able to login to PostfixAdmin in this case // - don't use dovecot:* methods that include the username in the hash - you won't be able to login to PostfixAdmin in this case
// - you'll need at least dovecot 2.1 for salted passwords ('doveadm pw' 2.0.x doesn't support the '-t' option) // - you'll need at least dovecot 2.1 for salted passwords ('doveadm pw' 2.0.x doesn't support the '-t' option)
// - dovecot 2.0.0 - 2.0.7 is not supported // - dovecot 2.0.0 - 2.0.7 is not supported
// - php_crypt PREFIX: hash has specified prefix - example: php_crypt:SHA512::{SHA256-CRYPT}
// sha512.b64 - {SHA512-CRYPT.B64} (base64 encoded sha512) (no dovecot dependency; should support migration from md5crypt) // sha512.b64 - {SHA512-CRYPT.B64} (base64 encoded sha512) (no dovecot dependency; should support migration from md5crypt)
$CONF['encrypt'] = 'md5crypt'; $CONF['encrypt'] = 'md5crypt';

@ -1061,12 +1061,15 @@ function _pacrypt_dovecot($pw, $pw_db = '') {
/** /**
* Supports DES, MD5, BLOWFISH, SHA256, SHA512 methods. * Supports DES, MD5, BLOWFISH, SHA256, SHA512 methods.
* *
* Via config we support an optional prefix (e.g. if you need hashes to start with {SHA256-CRYPT} and optional rounds (hardness) setting.
*
* @param string $pw * @param string $pw
* @param string $pw_db (can be empty if setting a new password) * @param string $pw_db (can be empty if setting a new password)
* @return string crypt'ed password; if it matches $pw_db then $pw is the original password. * @return string crypt'ed password; if it matches $pw_db then $pw is the original password.
*/ */
function _pacrypt_php_crypt($pw, $pw_db) { function _pacrypt_php_crypt($pw, $pw_db)
global $CONF; {
$configEncrypt = Config::read_string('encrypt');
// use PHPs crypt(), which uses the system's crypt() // use PHPs crypt(), which uses the system's crypt()
// same algorithms as used in /etc/shadow // same algorithms as used in /etc/shadow
@ -1074,31 +1077,48 @@ function _pacrypt_php_crypt($pw, $pw_db) {
// the algorithm for a new hash is chosen by feeding a salt with correct magic to crypt() // the algorithm for a new hash is chosen by feeding a salt with correct magic to crypt()
// set $CONF['encrypt'] to 'php_crypt' to use the default SHA512 crypt method // set $CONF['encrypt'] to 'php_crypt' to use the default SHA512 crypt method
// set $CONF['encrypt'] to 'php_crypt:METHOD' to use another method; methods supported: DES, MD5, BLOWFISH, SHA256, SHA512 // set $CONF['encrypt'] to 'php_crypt:METHOD' to use another method; methods supported: DES, MD5, BLOWFISH, SHA256, SHA512
// set $CONF['encrypt'] to 'php_crypt:METHOD:difficulty' where difficulty is between 1000-999999999
// set $CONF['encrypt'] to 'php_crypt:METHOD:difficulty:PREFIX' to prefix the hash with the {PREFIX} etc.
// tested on linux // tested on linux
$prefix = '';
if (strlen($pw_db) > 0) { if (strlen($pw_db) > 0) {
// existing pw provided. send entire password hash as salt for crypt() to figure out // existing pw provided. send entire password hash as salt for crypt() to figure out
$salt = $pw_db; $salt = $pw_db;
// if there was a prefix in the password, use this (override anything given in the config).
if (preg_match('/^\{([-A-Z0-9]+)\}(.+)$/', $pw_db, $method_matches)) {
$salt = $method_matches[2];
$prefix = "{" . $method_matches[1] . "}";
}
} else { } else {
$salt_method = 'SHA512'; // hopefully a reasonable default (better than MD5) $salt_method = 'SHA512'; // hopefully a reasonable default (better than MD5)
$hash_difficulty = ''; $hash_difficulty = '';
// no pw provided. create new password hash // no pw provided. create new password hash
if (strpos($CONF['encrypt'], ':') !== false) { if (strpos($configEncrypt, ':') !== false) {
// use specified hash method // use specified hash method
$split_method = explode(':', $CONF['encrypt']); $spec = explode(':', $configEncrypt);
$salt_method = $split_method[1]; $salt_method = $spec[1];
if (count($split_method) >= 3) { if (isset($spec[2])) {
$hash_difficulty = $split_method[2]; $hash_difficulty = $spec[2];
}
if (isset($spec[3])) {
$prefix = $spec[3]; // hopefully something like {SHA256-CRYPT}
} }
} }
// create appropriate salt for selected hash method // create appropriate salt for selected hash method
$salt = _php_crypt_generate_crypt_salt($salt_method, $hash_difficulty); $salt = _php_crypt_generate_crypt_salt($salt_method, $hash_difficulty);
} }
// send it to PHPs crypt()
$password = crypt($pw, $salt); $password = crypt($pw, $salt);
return $password;
return "{$prefix}{$password}";
} }
/** /**
* @param string $hash_type must be one of: MD5, DES, BLOWFISH, SHA256 or SHA512 (default) * @param string $hash_type must be one of: MD5, DES, BLOWFISH, SHA256 or SHA512 (default)
* @param int hash difficulty * @param int hash difficulty

@ -21,7 +21,7 @@
function update_string_list() { function update_string_list() {
for file in en.lang $filelist ; do for file in en.lang $filelist ; do
echo "<?php \$CONF['admin_name'] = ''; include('$file'); print join(\"\\n\", array_keys(\$PALANG)) . \"\\n\"; ?>" | php > $file.strings echo "<?php include('$file'); print join(\"\\n\", array_keys(\$PALANG)) . \"\\n\"; ?>" | php > $file.strings
done done
for file in $filelist ; do for file in $filelist ; do

@ -311,10 +311,7 @@ class AliasHandler extends PFAHandler {
protected function read_from_db_postprocess($db_result) { protected function read_from_db_postprocess($db_result) {
foreach ($db_result as $key => $value) { foreach ($db_result as $key => $value) {
# split comma-separated 'goto' into an array # split comma-separated 'goto' into an array
$goto = $db_result[$key]['goto'] ?? null; $db_result[$key]['goto'] = explode(',', $db_result[$key]['goto']);
if (is_string($goto)) {
$db_result[$key]['goto'] = explode(',', $goto);
}
# Vacation enabled? # Vacation enabled?
list($db_result[$key]['on_vacation'], $db_result[$key]['goto']) = remove_from_array($db_result[$key]['goto'], $this->getVacationAlias()); list($db_result[$key]['on_vacation'], $db_result[$key]['goto']) = remove_from_array($db_result[$key]['goto'], $this->getVacationAlias());

@ -50,22 +50,33 @@ final class Config {
$newConfig = $_this->getAll(); $newConfig = $_this->getAll();
foreach ($config as $name => $value) { foreach ($config as $names => $value) {
$newConfig[$name] = $value; $name = $_this->__configVarNames($names);
switch (count($name)) {
case 3:
$newConfig[$name[0]][$name[1]][$name[2]] = $value;
break;
case 2:
$newConfig[$name[0]][$name[1]] = $value;
break;
case 1:
$newConfig[$name[0]] = $value;
break;
}
} }
$_this->setAll($newConfig); $_this->setAll($newConfig);
} }
/** /**
* @param string $var
* @return array * @return array
* @param string $var
*/ */
public static function read_array($var) { public static function read_array($var) {
$stuff = self::read($var); $stuff = self::read($var);
if (!is_array($stuff)) { if (!is_array($stuff)) {
trigger_error('In ' . __FUNCTION__ . ": expected config $var to be an array, but received a " . gettype($stuff), E_USER_ERROR); trigger_error('In '.__FUNCTION__.": expected config $var to be an array, but received a " . gettype($stuff), E_USER_ERROR);
} }
return $stuff; return $stuff;
@ -83,7 +94,7 @@ final class Config {
} }
if (!is_string($stuff)) { if (!is_string($stuff)) {
trigger_error('In ' . __FUNCTION__ . ": expected config $var to be a string, but received a " . gettype($stuff), E_USER_ERROR); trigger_error('In '.__FUNCTION__.": expected config $var to be a string, but received a " . gettype($stuff), E_USER_ERROR);
return ''; return '';
} }
@ -110,12 +121,34 @@ final class Config {
return $config; return $config;
} }
if (isset($config[$var])) { $name = $_this->__configVarNames($var);
return $config[$var];
switch (count($name)) {
case 3:
$zero = $name[0];
$one = $name[1];
$two = $name[2];
if (isset($config[$zero], $config[$zero][$one], $config[$zero][$one][$two])) {
return $config[$zero][$one][$two];
}
break;
case 2:
$zero = $name[0];
$one = $name[1];
if (isset($config[$zero], $config[$zero][$one])) {
return $config[$zero][$one];
}
break;
case 1:
$zero = $name[0];
if (isset($config[$zero])) {
return $config[$zero];
}
break;
} }
if (!in_array($var, self::$deprecated_options)) { if (!in_array(join('.', $name), self::$deprecated_options)) {
error_log('Config::read(): attempt to read undefined config option "' . $var . '", returning null'); error_log('Config::read(): attempt to read undefined config option "' . join('.', $name) . '", returning null');
} }
return null; return null;
@ -161,7 +194,7 @@ final class Config {
} }
if (!is_string($value)) { if (!is_string($value)) {
trigger_error('In ' . __FUNCTION__ . ": expected config $var to be a string, but received a " . gettype($value), E_USER_ERROR); trigger_error('In '.__FUNCTION__.": expected config $var to be a string, but received a " . gettype($value), E_USER_ERROR);
error_log("config $var should be a string, found: " . json_encode($value)); error_log("config $var should be a string, found: " . json_encode($value));
return false; return false;
} }
@ -189,6 +222,7 @@ final class Config {
} }
/** /**
* Get translated text from $PALANG * Get translated text from $PALANG
* (wrapper for self::read(), see also the comments there) * (wrapper for self::read(), see also the comments there)
@ -198,14 +232,15 @@ final class Config {
* @access public * @access public
*/ */
public static function lang($var) { public static function lang($var) {
$languages = self::read_array('__LANG'); $value = self::read("__LANG.{$var}");
$value = $languages[$var] ?? ''; if (is_null($value)) {
return '';
}
if (!is_string($value)) { if (!is_string($value)) {
trigger_error('In ' . __FUNCTION__ . ": expected config $var to be a string , but received a " . gettype($value), E_USER_ERROR); trigger_error('In '.__FUNCTION__.": expected config $var to be a string , but received a " . gettype($value), E_USER_ERROR);
} }
return $value; return $value;
} }
@ -218,18 +253,7 @@ final class Config {
* @return string value of $PALANG[$var], parsed by sprintf * @return string value of $PALANG[$var], parsed by sprintf
*/ */
public static function lang_f($var, $value) { public static function lang_f($var, $value) {
$all = self::read_array('__LANG'); return self::read_f('__LANG.'. $var, $value);
$text = $all[$var] ?? '';
$newtext = sprintf($text, $value);
# check if sprintf changed something - if not, there are chances that $text didn't contain a %s
if ($text == $newtext) {
error_log("$var used via read_f, but nothing replaced (value $value)");
}
return $newtext;
} }
/** /**
@ -246,6 +270,23 @@ final class Config {
public function setAll(array $config) { public function setAll(array $config) {
$this->config = $config; $this->config = $config;
} }
/**
* Checks $name for dot notation to create dynamic Configure::$var as an array when needed.
*
* @param mixed $name Name to split
* @return array Name separated in items through dot notation
* @access private
*/
private function __configVarNames($name) {
if (is_string($name)) {
if (strpos($name, ".")) {
return explode(".", $name);
}
return array($name);
}
return $name;
}
} }
/* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */ /* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */

@ -227,11 +227,6 @@ class MailboxHandler extends PFAHandler {
$this->values['quota'] = $this->values['quota'] * $multiplier; # convert quota from MB to bytes $this->values['quota'] = $this->values['quota'] * $multiplier; # convert quota from MB to bytes
} }
// Avoid trying to store '' in an integer field
if($this->values['quota'] === '') {
$this->values['quota'] = 0;
}
$ah = new AliasHandler($this->new, $this->admin_username); $ah = new AliasHandler($this->new, $this->admin_username);
$ah->calledBy('MailboxHandler'); $ah->calledBy('MailboxHandler');

@ -37,12 +37,11 @@ require(dirname(__FILE__) . '/../templates/header.php');
// //
$f_phpversion = function_exists("phpversion"); $f_phpversion = function_exists("phpversion");
$f_apache_get_version = function_exists("apache_get_version"); $f_apache_get_version = function_exists("apache_get_version");
$f_mysql_connect = function_exists("mysql_connect");
$m_pdo = extension_loaded("PDO"); $f_mysqli_connect = function_exists("mysqli_connect");
$m_pdo_mysql = extension_loaded("pdo_mysql"); $f_pg_connect = function_exists("pg_connect");
$m_pdo_pgsql = extension_loaded('pdo_pgsql'); $f_sqlite_open = class_exists("SQLite3");
$m_pdo_sqlite= extension_loaded("pdo_sqlite"); $f_pdo = class_exists('PDO');
$f_session_start = function_exists("session_start"); $f_session_start = function_exists("session_start");
$f_preg_match = function_exists("preg_match"); $f_preg_match = function_exists("preg_match");
$f_mb_encode_mimeheader = function_exists("mb_encode_mimeheader"); $f_mb_encode_mimeheader = function_exists("mb_encode_mimeheader");
@ -106,42 +105,68 @@ require(dirname(__FILE__) . '/../templates/header.php');
print "Create the file, and edit as appropriate (e.g. select database type etc)<br />"; print "Create the file, and edit as appropriate (e.g. select database type etc)<br />";
} }
//
// Check if there is support for at least 1 database // Check if there is support for at least 1 database
if (($m_pdo == 0) and ($m_pdo_mysql == 0) and ($m_pdo_sqlite == 0) and ($m_pdo_psql == 0) ) { //
print "<li><b>Error: There is no database (PDO) support in your PHP setup</b><br />\n"; if (($f_pdo == 0) and ($f_mysql_connect == 0) and ($f_mysqli_connect == 0) and ($f_pg_connect == 0) and ($f_sqlite_open == 0)) {
print "<span style='color: red'> print "<li><b>Error: There is no database support in your PHP setup</b><br />\n";
You MUST install a suitable PHP PDO extension (e.g. pdo_pgsql, pdo_mysql or pdo_sqlite). print "To install MySQL 3.23 or 4.0 support on FreeBSD:<br />\n";
</span>\n</li>"; print "<pre>% cd /usr/ports/databases/php{$phpversion}-mysql/\n";
print "% make clean install\n";
print " - or with portupgrade -\n";
print "% portinstall php{$phpversion}-mysql</pre>\n";
if ($phpversion >= 5) {
print "To install MySQL 4.1 support on FreeBSD:<br />\n";
print "<pre>% cd /usr/ports/databases/php5-mysqli/\n";
print "% make clean install\n";
print " - or with portupgrade -\n";
print "% portinstall php5-mysqli</pre>\n";
}
print "To install PostgreSQL support on FreeBSD:<br />\n";
print "<pre>% cd /usr/ports/databases/php{$phpversion}-pgsql/\n";
print "% make clean install\n";
print " - or with portupgrade -\n";
print "% portinstall php{$phpversion}-pgsql</pre></li>\n";
$error += 1; $error += 1;
} }
if ($m_pdo_mysql == 1) { if ($f_mysqli_connect == 1) {
print "<li>Database - PDO MySQL - Found</li>"; print "<li>Database - MySQL (mysqli_ functions) - Found\n";
if (Config::read_string('database_type') != 'mysqli') {
print "<br>(change the database_type to 'mysqli' in config.local.php if you want to use MySQL)\n";
}
print "</li>";
} else { } else {
print "<li>Database - MySQL (pdo_mysql) extension not found</li>"; print "<li>Database - MySQL (mysqli_ functions) - Not found</li>";
}
if (Config::read_string('database_type') == 'mysql') {
print "<li><strong><span style='color: red'>Warning:</span> your configured database_type 'mysql' is deprecated; you must move to use 'mysqli'</strong> in your config.local.php.</li>\n";
$error++;
} }
// //
// PostgreSQL functions // PostgreSQL functions
// //
if ($m_pdo_pgsql == 1) { if ($f_pg_connect == 1) {
print "<li>Database : PDO PostgreSQL - Found \n"; print "<li>Database : PostgreSQL support (pg_ functions) - Found\n";
if (Config::read_string('database_type') != 'pgsql') { if (Config::read_string('database_type') != 'pgsql') {
print "<br>(change the database_type to 'pgsql' in config.local.php if you want to use PostgreSQL)\n"; print "<br>(change the database_type to 'pgsql' in config.local.php if you want to use PostgreSQL)\n";
} }
print "</li>"; print "</li>";
} else { } else {
print "<li>Database - PostgreSQL (pdo_pgsql) extension not found</li>"; print "<li>Database - PostgreSQL (pg_ functions) - Not found</li>";
} }
if ($m_pdo_sqlite == 1) { if ($f_sqlite_open == 1) {
print "<li>Database : PDO SQLite - Found \n"; print "<li>Database : SQLite support (SQLite3) - Found \n";
if (Config::read_string('database_type') != 'sqlite') { if (Config::read_string('database_type') != 'sqlite') {
print "<br>(change the database_type to 'sqlite' in config.local.php if you want to use SQLite)\n"; print "<br>(change the database_type to 'sqlite' in config.local.php if you want to use SQLite)\n";
} }
print "</li>"; print "</li>";
} else { } else {
print "<li>Database - SQLite (pdo_sqlite) extension not found</li>"; print "<li>Database - SQLite (SQLite3) - Not found</li>";
} }
// //
@ -201,8 +226,8 @@ require(dirname(__FILE__) . '/../templates/header.php');
if ($f_mb_encode_mimeheader == 1) { if ($f_mb_encode_mimeheader == 1) {
print "<li>Depends on: multibyte string - Found</li>\n"; print "<li>Depends on: multibyte string - Found</li>\n";
} else { } else {
print "<li><b>Error: Depends on: multibyte string - mbstring extension missing.</b><br />\n"; print "<li><b>Error: Depends on: multibyte string - NOT FOUND</b><br />\n";
print "To install multibyte string support, perhaps install php$phpversion-mbstring</li>\n"; print "To install multibyte string support, install php$phpversion-mbstring</li>\n";
$error += 1; $error += 1;
} }
@ -213,8 +238,8 @@ require(dirname(__FILE__) . '/../templates/header.php');
if ($f_imap_open == 1) { if ($f_imap_open == 1) {
print "<li>IMAP functions - Found</li>\n"; print "<li>IMAP functions - Found</li>\n";
} else { } else {
print "<li><b>Warning: Optional dependency 'imap' extension missing</b><br />\n"; print "<li><b>Warning: May depend on: IMAP functions - Not Found</b><br />\n";
print "To install IMAP support, perhaps install php$phpversion-imap<br />\n"; print "To install IMAP support, install php$phpversion-imap<br />\n";
print "Without IMAP support, you won't be able to create subfolders when creating mailboxes.</li>\n"; print "Without IMAP support, you won't be able to create subfolders when creating mailboxes.</li>\n";
} }

@ -103,7 +103,7 @@ function _db_add_field($table, $field, $fieldtype, $after = '') {
function echo_out($text) { function echo_out($text) {
if (defined('PHPUNIT_TEST')) { if (defined('PHPUNIT_TEST')) {
//error_log("" . $text); error_log("" . $text);
} else { } else {
echo $text . "\n"; echo $text . "\n";
} }

@ -314,21 +314,34 @@ class PostfixAdmin {
* @param array $params Parameters to parse * @param array $params Parameters to parse
*/ */
public function parseParams($params) { public function parseParams($params) {
$this->__parseParams($params);
}
/**
* Helper for recursively parsing params
*/
private function __parseParams($params) {
$count = count($params); $count = count($params);
for ($i = 0; $i < $count; $i++) { for ($i = 0; $i < $count; $i++) {
if ($params[$i] != '' && $params[$i]{0} === '-' && $params[$i] != '-1') { if (isset($params[$i])) {
if ($params[$i] != '' && $params[$i]{0} === '-') {
$key = substr($params[$i], 1); $key = substr($params[$i], 1);
if (isset($params[$i+1])) {
# TODO: ideally we should know if a parameter can / must have a value instead of whitelisting known valid values starting with '-' (probably only bool doesn't need a value)
if ($params[$i+1]{0} === '-' && $params[$i+1] != '-1') {
$this->params[$key] = true; $this->params[$key] = true;
unset($params[$i]);
if (isset($params[++$i])) {
# TODO: ideally we should know if a parameter can / must have a value instead of whitelisting known valid values starting with '-' (probably only bool doesn't need a value)
if ($params[$i]{0} !== '-' or $params[$i] != '-1') {
$this->params[$key] = $params[$i];
unset($params[$i]);
} else { } else {
$this->params[$key] = $params[$i+1]; $i--;
$i++; $this->__parseParams($params);
} }
} }
} else { } else {
$this->args[] = $params[$i]; $this->args[] = $params[$i];
unset($params[$i]);
}
} }
} }
} }

@ -9,7 +9,7 @@ class AliasHandlerTest extends \PHPUnit\Framework\TestCase {
$this->assertEmpty($results); $this->assertEmpty($results);
} }
public function tearDown() : void { public function tearDown() {
$_SESSION = []; $_SESSION = [];
db_query('DELETE FROM alias'); db_query('DELETE FROM alias');
db_query('DELETE FROM domain'); db_query('DELETE FROM domain');

@ -1,33 +0,0 @@
<?php
class ConfigTest extends \PHPUnit\Framework\TestCase {
public function setUp() : void {
$c = Config::getInstance();
$all = $c->getAll();
$all['xmlrpc_enabled'] = false;
$c->setAll($all);
parent::setUp();
}
public function testLangF() {
$x = Config::lang_f('must_be_numeric', 'foo@bar');
$this->assertEquals('foo@bar must be numeric', $x);
}
public function testLang() {
$x = Config::lang('must_be_numeric', 'foo@bar');
$this->assertEquals('%s must be numeric', $x);
}
public function testBool() {
$x = Config::bool('xmlrpc_enabled');
$this->assertFalse($x);
}
}

@ -1,7 +1,7 @@
<?php <?php
class CreatePageBrowserTest extends \PHPUnit\Framework\TestCase { class CreatePageBrowserTest extends \PHPUnit\Framework\TestCase {
public function tearDown() : void { public function tearDown() {
$this->cleanup(); $this->cleanup();
} }

@ -3,7 +3,7 @@
class DbBasicTest extends \PHPUnit\Framework\TestCase { class DbBasicTest extends \PHPUnit\Framework\TestCase {
private $test_domain; private $test_domain;
public function setUp() : void { public function setUp() {
$db = db_connect(); $db = db_connect();
$test_domain = 'test' . uniqid() . '.com'; $test_domain = 'test' . uniqid() . '.com';
$this->test_domain = $test_domain; $this->test_domain = $test_domain;

@ -1,7 +1,7 @@
<?php <?php
class MailboxHandlerTest extends \PHPUnit\Framework\TestCase { class MailboxHandlerTest extends \PHPUnit\Framework\TestCase {
public function tearDown() : void { public function tearDown() {
db_query('DELETE FROM mailbox'); db_query('DELETE FROM mailbox');
db_query('DELETE FROM domain'); db_query('DELETE FROM domain');
db_query('DELETE FROM domain_admins'); db_query('DELETE FROM domain_admins');

@ -1,7 +1,9 @@
<?php <?php
class PaCryptTest extends \PHPUnit\Framework\TestCase { class PaCryptTest extends \PHPUnit\Framework\TestCase
public function testMd5Crypt() { {
public function testMd5Crypt()
{
$hash = _pacrypt_md5crypt('test', ''); $hash = _pacrypt_md5crypt('test', '');
$this->assertNotEmpty($hash); $this->assertNotEmpty($hash);
@ -10,7 +12,8 @@ class PaCryptTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals($hash, _pacrypt_md5crypt('test', $hash)); $this->assertEquals($hash, _pacrypt_md5crypt('test', $hash));
} }
public function testCrypt() { public function testCrypt()
{
// E_NOTICE if we pass in '' for the salt // E_NOTICE if we pass in '' for the salt
$hash = _pacrypt_crypt('test', 'sa'); $hash = _pacrypt_crypt('test', 'sa');
@ -21,7 +24,8 @@ class PaCryptTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals($hash, _pacrypt_crypt('test', $hash)); $this->assertEquals($hash, _pacrypt_crypt('test', $hash));
} }
public function testMySQLEncrypt() { public function testMySQLEncrypt()
{
if (!db_mysql()) { if (!db_mysql()) {
$this->markTestSkipped('Not using MySQL'); $this->markTestSkipped('Not using MySQL');
} }
@ -45,7 +49,8 @@ class PaCryptTest extends \PHPUnit\Framework\TestCase {
); );
} }
public function testAuthlib() { public function testAuthlib()
{
global $CONF; global $CONF;
// too many options! // too many options!
@ -66,7 +71,8 @@ class PaCryptTest extends \PHPUnit\Framework\TestCase {
} }
} }
public function testPacryptDovecot() { public function testPacryptDovecot()
{
global $CONF; global $CONF;
if (!file_exists('/usr/bin/doveadm')) { if (!file_exists('/usr/bin/doveadm')) {
$this->markTestSkipped("No /usr/bin/doveadm"); $this->markTestSkipped("No /usr/bin/doveadm");
@ -82,9 +88,8 @@ class PaCryptTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals($expected_hash, _pacrypt_dovecot('test', $expected_hash)); $this->assertEquals($expected_hash, _pacrypt_dovecot('test', $expected_hash));
} }
public function testPhpCrypt() { public function testPhpCrypt()
global $CONF; {
$config = Config::getInstance(); $config = Config::getInstance();
Config::write('encrypt', 'php_crypt:MD5'); Config::write('encrypt', 'php_crypt:MD5');
@ -99,11 +104,44 @@ class PaCryptTest extends \PHPUnit\Framework\TestCase {
$fail = _pacrypt_php_crypt('bar', $expected); $fail = _pacrypt_php_crypt('bar', $expected);
}
public function testPhpCryptHandlesPrefixAndOrRounds()
{
// try with 1000 rounds
Config::write('encrypt', 'php_crypt:SHA256:1000');
$password = 'hello';
$randomHash = '$5$VhqhhsXJtPFeBX9e$kz3/CMIEu80bKdtDAcISIrDfdwtc.ilR68Vb3hNhu/7';
$randomHashWithPrefix = '{SHA256-CRYPT}' . $randomHash;
$new = _pacrypt_php_crypt($password, '');
$this->assertNotEquals($randomHash, $new); // salts should be different.
$enc = _pacrypt_php_crypt($password, $randomHash);
$this->assertEquals($enc, $randomHash);
$this->assertEquals($randomHash, _pacrypt_php_crypt("hello", $randomHash));
$this->assertEquals($randomHash, _pacrypt_crypt("hello", $randomHash));
Config::write('encrypt', 'php_crypt:SHA256::{SHA256-CRYPT}');
$enc = _pacrypt_php_crypt("hello", $randomHash);
$this->assertEquals($randomHash, $enc); // we passed in something lacking the prefix, so we shouldn't have added it in.
$this->assertTrue(hash_equals($randomHash, $enc));
// should cope with this :
$enc = _pacrypt_php_crypt($password, '');
$this->assertEquals($enc, _pacrypt_php_crypt($password, $enc));
$this->assertNotEquals($fail, $expected); $this->assertRegExp('/^\{SHA256-CRYPT\}/', $enc);
$this->assertGreaterThan(20, strlen($enc));
} }
public function testPhpCryptRandomString() { public function testPhpCryptRandomString()
{
$str1 = _php_crypt_random_string('abcdefg123456789', 2); $str1 = _php_crypt_random_string('abcdefg123456789', 2);
$str2 = _php_crypt_random_string('abcdefg123456789', 2); $str2 = _php_crypt_random_string('abcdefg123456789', 2);
$str3 = _php_crypt_random_string('abcdefg123456789', 2); $str3 = _php_crypt_random_string('abcdefg123456789', 2);
@ -114,10 +152,11 @@ class PaCryptTest extends \PHPUnit\Framework\TestCase {
// it should be difficult for us to get three salts of the same value back... // it should be difficult for us to get three salts of the same value back...
// not impossible though. // not impossible though.
$this->assertFalse( strcmp($str1, $str2) == 0 && strcmp($str1, $str3) == 0 ); $this->assertFalse(strcmp($str1, $str2) == 0 && strcmp($str1, $str3) == 0);
} }
public function testSha512B64() { public function testSha512B64()
{
$str1 = _pacrypt_sha512_b64('test', ''); $str1 = _pacrypt_sha512_b64('test', '');
$str2 = _pacrypt_sha512_b64('test', ''); $str2 = _pacrypt_sha512_b64('test', '');
@ -138,6 +177,6 @@ class PaCryptTest extends \PHPUnit\Framework\TestCase {
$this->assertFalse(hash_equals('test', $str3)); $this->assertFalse(hash_equals('test', $str3));
$this->assertTrue(hash_equals(_pacrypt_sha512_b64('foo',$str3), $str3)); $this->assertTrue(hash_equals(_pacrypt_sha512_b64('foo', $str3), $str3));
} }
} }

@ -13,6 +13,17 @@ class RemoteAliasTest extends RemoteTest {
global $CONF; global $CONF;
} }
/**
* Adds the test recipient data to the database.
*/
public function setUp() {
parent::setUp();
}
public function tearDown() {
parent::tearDown();
}
public function testGet() { public function testGet() {
/* although we created an alias record, for users, this isn't returned... */ /* although we created an alias record, for users, this isn't returned... */
$this->assertEquals($this->alias->get(), array()); $this->assertEquals($this->alias->get(), array());

@ -12,7 +12,7 @@ abstract class RemoteTest extends \PHPUnit\Framework\TestCase {
protected $xmlrpc_client; protected $xmlrpc_client;
public function setUp() : void { public function setUp() {
parent::setUp(); parent::setUp();
if ($this->server_url == 'http://change.me/to/work') { if ($this->server_url == 'http://change.me/to/work') {

@ -13,7 +13,7 @@ class RemoteVacationTest extends RemoteTest {
/** /**
* Adds the test recipient data to the database. * Adds the test recipient data to the database.
*/ */
public function setUp() : void { public function setUp() {
// Ensure config.inc.php is vaguely correct. // Ensure config.inc.php is vaguely correct.
global $CONF; global $CONF;
if ($CONF['vacation'] != 'YES' || $CONF['vacation_control'] != "YES") { if ($CONF['vacation'] != 'YES' || $CONF['vacation_control'] != "YES") {
@ -36,7 +36,7 @@ class RemoteVacationTest extends RemoteTest {
public function testGetDetails() { public function testGetDetails() {
$details = $this->vacation->getDetails(); $details = $this->vacation->getDetails();
$this->assertFalse($details); // empty by default (thanks to tearDown/setUp); $this->assertFalse($details); // empty by default (thansk to tearDown/setUp);
} }
public function testSetAway() { public function testSetAway() {

@ -22,7 +22,7 @@ if (getenv('DATABASE') == 'sqlite' || getenv('DATABASE') == false) {
} }
touch($db_file); touch($db_file);
error_log("Using: SQLite database for tests - $db_file"); echo "Using: SQLite database for tests - $db_file \n";
} }
if (getenv('DATABASE') == 'postgresql') { if (getenv('DATABASE') == 'postgresql') {
$user = getenv('PGUSER') ?: 'postgres'; $user = getenv('PGUSER') ?: 'postgres';
@ -40,7 +40,7 @@ if (getenv('DATABASE') == 'postgresql') {
Config::write('database_name', 'postfixadmin'); Config::write('database_name', 'postfixadmin');
Config::write('database_host', $host); Config::write('database_host', $host);
error_log("Using: PostgreSQL database for tests\n"); echo "Using: PostgreSQL database for tests\n";
} }
if (getenv('DATABASE') == 'mysql') { if (getenv('DATABASE') == 'mysql') {
@ -76,7 +76,7 @@ if (getenv('DATABASE') == 'mysql') {
Config::write('database_password', $config['password']); Config::write('database_password', $config['password']);
Config::write('database_name', 'postfixadmin'); Config::write('database_name', 'postfixadmin');
error_log("Using: MySQL database for tests"); echo "Using: MySQL database for tests\n";
} }
try { try {

Loading…
Cancel
Save