From 880d037dcc60a6b04aed0ffde2cbe1e4244ede7b Mon Sep 17 00:00:00 2001
From: Felix Ableitner
+ * $sql = "SELECT * FROM foo WHERE x = '" . escape_string('fish') . "'";
+ *
+ *
+ * @param int|string $string parameters to escape
+ * @return string cleaned data, suitable for use within an SQL statement.
+ */
+function escape_string($string_or_int) {
+ $link = db_connect();
+ $string_or_int = (string) $string_or_int;
+ $quoted = $link->quote($string_or_int);
+ return trim($quoted, "'");
+}
+
+
+/**
+ * safeget
+ * Action: get value from $_GET[$param], or $default if $_GET[$param] is not set
+ * Call: $param = safeget('param') # replaces $param = $_GET['param']
+ * - or -
+ * $param = safeget('param', 'default')
+ *
+ * @param string $param parameter name.
+ * @param string $default (optional) - default value if key is not set.
+ * @return string
+ */
+function safeget($param, $default = "") {
+ $retval = $default;
+ if (isset($_GET[$param])) {
+ $retval = $_GET[$param];
+ }
+ return $retval;
+}
+
+/**
+ * safepost - similar to safeget() but for $_POST
+ * @see safeget()
+ * @param string $param parameter name
+ * @param string $default (optional) default value (defaults to "")
+ * @return string|array - value in $_POST[$param] or $default
+ */
+function safepost($param, $default = "") {
+ $retval = $default;
+ if (isset($_POST[$param])) {
+ $retval = $_POST[$param];
+ }
+ return $retval;
+}
+
+/**
+ * safeserver
+ * @see safeget()
+ * @param string $param
+ * @param string $default (optional)
+ * @return string value from $_SERVER[$param] or $default
+ */
+function safeserver($param, $default = "") {
+ $retval = $default;
+ if (isset($_SERVER[$param])) {
+ $retval = $_SERVER[$param];
+ }
+ return $retval;
+}
+
+/**
+ * safecookie
+ * @see safeget()
+ * @param string $param
+ * @param string $default (optional)
+ * @return string value from $_COOKIE[$param] or $default
+ */
+function safecookie($param, $default = "") {
+ $retval = $default;
+ if (isset($_COOKIE[$param])) {
+ $retval = $_COOKIE[$param];
+ }
+ return $retval;
+}
+
+/**
+ * safesession
+ * @see safeget()
+ * @param string $param
+ * @param string $default (optional)
+ * @return string value from $_SESSION[$param] or $default
+ */
+function safesession($param, $default = "") {
+ $retval = $default;
+ if (isset($_SESSION[$param])) {
+ $retval = $_SESSION[$param];
+ }
+ return $retval;
+}
+
+
+/**
+ * pacol
+ * @param int $allow_editing
+ * @param int $display_in_form
+ * @param int display_in_list
+ * @param string $type
+ * @param string PALANG_label
+ * @param string PALANG_desc
+ * @param any optional $default
+ * @param array $options optional options
+ * @param int or $not_in_db - if array, can contain the remaining parameters as associated array. Otherwise counts as $not_in_db
+ * @return array for $struct
+ */
+function pacol($allow_editing, $display_in_form, $display_in_list, $type, $PALANG_label, $PALANG_desc, $default = "", $options = array(), $multiopt=0, $dont_write_to_db=0, $select="", $extrafrom="", $linkto="") {
+ if ($PALANG_label != '') {
+ $PALANG_label = Config::lang($PALANG_label);
+ }
+ if ($PALANG_desc != '') {
+ $PALANG_desc = Config::lang($PALANG_desc);
+ }
+
+ if (is_array($multiopt)) { # remaining parameters provided in named array
+ $not_in_db = 0; # keep default value
+ foreach ($multiopt as $key => $value) {
+ $$key = $value; # extract everything to the matching variable
+ }
+ } else {
+ $not_in_db = $multiopt;
+ }
+
+ return array(
+ 'editable' => $allow_editing,
+ 'display_in_form' => $display_in_form,
+ 'display_in_list' => $display_in_list,
+ 'type' => $type,
+ 'label' => $PALANG_label, # $PALANG field label
+ 'desc' => $PALANG_desc, # $PALANG field description
+ 'default' => $default,
+ 'options' => $options,
+ 'not_in_db' => $not_in_db,
+ 'dont_write_to_db' => $dont_write_to_db,
+ 'select' => $select, # replaces the field name after SELECT
+ 'extrafrom' => $extrafrom, # added after FROM xy - useful for JOINs etc.
+ 'linkto' => $linkto, # make the value a link - %s will be replaced with the ID
+ );
+}
+
+/**
+ * Action: Get all the properties of a domain.
+ * @param string $domain
+ * @return array
+ */
+function get_domain_properties($domain) {
+ $handler = new DomainHandler();
+ if (!$handler->init($domain)) {
+ die("Error: " . join("\n", $handler->errormsg));
+ }
+
+ if (!$handler->view()) {
+ die("Error: " . join("\n", $handler->errormsg));
+ }
+
+ $result = $handler->result();
+ return $result;
+}
+
+
+/**
+ * create_page_browser
+ * Action: Get page browser for a long list of mailboxes, aliases etc.
+ * Call: $pagebrowser = create_page_browser('table.field', 'query', 50) # replaces $param = $_GET['param']
+ *
+ * @param string $idxfield - database field name to use as title
+ * @param string $querypart - core part of the query (starting at "FROM")
+ * @return array
+ */
+function create_page_browser($idxfield, $querypart, $sql_params = []) {
+ global $CONF;
+ $page_size = (int) $CONF['page_size'];
+ $label_len = 2;
+ $pagebrowser = array();
+
+ $count_results = 0;
+
+ if ($page_size < 2) { # will break the page browser
+ die('$CONF[\'page_size\'] must be 2 or more!');
+ }
+
+ # get number of rows
+ $query = "SELECT count(*) as counter FROM (SELECT $idxfield $querypart) AS tmp";
+ $result = db_query_one($query, $sql_params);
+ if ($result && isset($result['counter'])) {
+ $count_results = $result['counter'] -1; # we start counting at 0, not 1
+ }
+
+ if ($count_results < $page_size) {
+ return array(); # only one page - no pagebrowser required
+ }
+
+ # init row counter
+ $initcount = "SET @r=-1";
+ if (db_pgsql()) {
+ $initcount = "CREATE TEMPORARY SEQUENCE rowcount MINVALUE 0";
+ }
+ if (!db_sqlite()) {
+ db_execute($initcount);
+ }
+
+ # get labels for relevant rows (first and last of each page)
+ $page_size_zerobase = $page_size - 1;
+ $query = "
+ SELECT * FROM (
+ SELECT $idxfield AS label, @r := @r + 1 AS 'r' $querypart
+ ) idx WHERE MOD(idx.r, $page_size) IN (0,$page_size_zerobase) OR idx.r = $count_results
+ ";
+
+ if (db_pgsql()) {
+ $query = "
+ SELECT * FROM (
+ SELECT $idxfield AS label, nextval('rowcount') AS row $querypart
+ ) idx WHERE MOD(idx.row, $page_size) IN (0,$page_size_zerobase) OR idx.row = $count_results
+ ";
+ }
+
+ if (db_sqlite()) {
+ $query = "
+ WITH idx AS (SELECT * $querypart)
+ SELECT $idxfield AS label, (SELECT (COUNT(*) - 1) FROM idx t1 WHERE t1.$idxfield <= t2.$idxfield) AS row
+ FROM idx t2
+ WHERE (row % $page_size) IN (0,$page_size_zerobase) OR row = $count_results";
+ }
+
+ # PostgreSQL:
+ # http://www.postgresql.org/docs/8.1/static/sql-createsequence.html
+ # http://www.postgresonline.com/journal/archives/79-Simulating-Row-Number-in-PostgreSQL-Pre-8.4.html
+ # http://www.pg-forum.de/sql/1518-nummerierung-der-abfrageergebnisse.html
+ # CREATE TEMPORARY SEQUENCE foo MINVALUE 0 MAXVALUE $page_size_zerobase CYCLE
+ # afterwards: DROP SEQUENCE foo
+
+ $result = db_query_all($query, $sql_params);
+ foreach ($result as $k => $row) {
+ if (isset($result[$k + 1])) {
+ $row2 = $result[$k + 1];
+ $label = substr($row['label'], 0, $label_len) . '-' . substr($row2['label'], 0, $label_len);
+ } else {
+ $label = substr($row['label'], 0, $label_len);
+ }
+ $pagebrowser[] = $label;
+ }
+
+ if (db_pgsql()) {
+ db_execute("DROP SEQUENCE rowcount");
+ }
+
+ return $pagebrowser;
+}
+
+
+/**
+ * Recalculates the quota from MBs to bytes (divide, /)
+ * @param int $quota
+ * @return float
+ */
+function divide_quota($quota) {
+ if ($quota == -1) {
+ return $quota;
+ }
+ $value = round($quota / (int) Config::read_string('quota_multiplier'), 2);
+ return $value;
+}
+
+
+/**
+ * Checks if the admin is the owner of the domain (or global-admin)
+ * @param string $username
+ * @param string $domain
+ * @return bool
+ */
+function check_owner($username, $domain) {
+ $table_domain_admins = table_by_key('domain_admins');
+
+ $result = db_query_all(
+ "SELECT 1 FROM $table_domain_admins WHERE username= ? AND (domain = ? OR domain = 'ALL') AND active = ?" ,
+ array($username, $domain, db_get_boolean(true))
+ );
+
+ if (sizeof($result) == 1 || sizeof($result) == 2) { # "ALL" + specific domain permissions is possible
+ # TODO: if superadmin, check if given domain exists in the database
+ return true;
+ } else {
+ if (sizeof($result) > 2) { # more than 2 results means something really strange happened...
+ flash_error("Permission check returned multiple results. Please go to 'edit admin' for your username and press the save "
+ . "button once to fix the database. If this doesn't help, open a bugreport.");
+ }
+ return false;
+ }
+}
+
+
+
+/**
+ * List domains for an admin user.
+ * @param String $username
+ * @return array of domain names.
+ */
+function list_domains_for_admin($username) {
+ $table_domain = table_by_key('domain');
+ $table_domain_admins = table_by_key('domain_admins');
+
+ $condition = array();
+
+ $E_username = escape_string($username);
+
+ $query = "SELECT $table_domain.domain FROM $table_domain ";
+ $condition[] = "$table_domain.domain != 'ALL'";
+
+ $pvalues = array();
+
+ $result = db_query_one("SELECT username FROM $table_domain_admins WHERE username= :username AND domain='ALL'", array('username' => $username));
+ if (empty($result)) { # not a superadmin
+ $pvalues['username'] = $username;
+ $pvalues['active'] = db_get_boolean(true);
+ $pvalues['backupmx'] = db_get_boolean(false);
+
+ $query .= " LEFT JOIN $table_domain_admins ON $table_domain.domain=$table_domain_admins.domain ";
+ $condition[] = "$table_domain_admins.username = :username ";
+ $condition[] = "$table_domain.active = :active "; # TODO: does it really make sense to exclude inactive...
+ $condition[] = "$table_domain.backupmx = :backupmx" ; # TODO: ... and backupmx domains for non-superadmins?
+ }
+
+ $query .= " WHERE " . join(' AND ', $condition);
+ $query .= " ORDER BY $table_domain.domain";
+
+ $result = db_query_all($query, $pvalues);
+
+ return array_column($result, 'domain');
+}
+
+
+if (!function_exists('array_column')) {
+ require_once(dirname(__FILE__) . '/lib/array_column.php');
+}
+
+/**
+ * List all available domains.
+ *
+ * @return array
+ */
+function list_domains() {
+ $list = array();
+
+ $table_domain = table_by_key('domain');
+ $result = db_query_all("SELECT domain FROM $table_domain WHERE domain!='ALL' ORDER BY domain");
+ $i = 0;
+ foreach ($result as $row) {
+ $list[$i] = $row['domain'];
+ $i++;
+ }
+ return $list;
+}
+
+
+
+
+//
+// list_admins
+// Action: Lists all the admins
+// Call: list_admins ()
+//
+// was admin_list_admins
+//
+function list_admins() {
+ $handler = new AdminHandler();
+
+ $handler->getList('');
+
+ return $handler->result();
+}
+
+
+
+//
+// encode_header
+// Action: Encode a string according to RFC 1522 for use in headers if it contains 8-bit characters.
+// Call: encode_header (string header, string charset)
+//
+function encode_header($string, $default_charset = "utf-8") {
+ if (strtolower($default_charset) == 'iso-8859-1') {
+ $string = str_replace("\240", ' ', $string);
+ }
+
+ $j = strlen($string);
+ $max_l = 75 - strlen($default_charset) - 7;
+ $aRet = array();
+ $ret = '';
+ $iEncStart = $enc_init = false;
+ $cur_l = $iOffset = 0;
+
+ for ($i = 0; $i < $j; ++$i) {
+ switch ($string{$i}) {
+ case '=':
+ case '<':
+ case '>':
+ case ',':
+ case '?':
+ case '_':
+ if ($iEncStart === false) {
+ $iEncStart = $i;
+ }
+ $cur_l+=3;
+ if ($cur_l > ($max_l-2)) {
+ $aRet[] = substr($string, $iOffset, $iEncStart-$iOffset);
+ $aRet[] = "=?$default_charset?Q?$ret?=";
+ $iOffset = $i;
+ $cur_l = 0;
+ $ret = '';
+ $iEncStart = false;
+ } else {
+ $ret .= sprintf("=%02X", ord($string{$i}));
+ }
+ break;
+ case '(':
+ case ')':
+ if ($iEncStart !== false) {
+ $aRet[] = substr($string, $iOffset, $iEncStart-$iOffset);
+ $aRet[] = "=?$default_charset?Q?$ret?=";
+ $iOffset = $i;
+ $cur_l = 0;
+ $ret = '';
+ $iEncStart = false;
+ }
+ break;
+ case ' ':
+ if ($iEncStart !== false) {
+ $cur_l++;
+ if ($cur_l > $max_l) {
+ $aRet[] = substr($string, $iOffset, $iEncStart-$iOffset);
+ $aRet[] = "=?$default_charset?Q?$ret?=";
+ $iOffset = $i;
+ $cur_l = 0;
+ $ret = '';
+ $iEncStart = false;
+ } else {
+ $ret .= '_';
+ }
+ }
+ break;
+ default:
+ $k = ord($string{$i});
+ if ($k > 126) {
+ if ($iEncStart === false) {
+ // do not start encoding in the middle of a string, also take the rest of the word.
+ $sLeadString = substr($string, 0, $i);
+ $aLeadString = explode(' ', $sLeadString);
+ $sToBeEncoded = array_pop($aLeadString);
+ $iEncStart = $i - strlen($sToBeEncoded);
+ $ret .= $sToBeEncoded;
+ $cur_l += strlen($sToBeEncoded);
+ }
+ $cur_l += 3;
+ // first we add the encoded string that reached it's max size
+ if ($cur_l > ($max_l-2)) {
+ $aRet[] = substr($string, $iOffset, $iEncStart-$iOffset);
+ $aRet[] = "=?$default_charset?Q?$ret?= ";
+ $cur_l = 3;
+ $ret = '';
+ $iOffset = $i;
+ $iEncStart = $i;
+ }
+ $enc_init = true;
+ $ret .= sprintf("=%02X", $k);
+ } else {
+ if ($iEncStart !== false) {
+ $cur_l++;
+ if ($cur_l > $max_l) {
+ $aRet[] = substr($string, $iOffset, $iEncStart-$iOffset);
+ $aRet[] = "=?$default_charset?Q?$ret?=";
+ $iEncStart = false;
+ $iOffset = $i;
+ $cur_l = 0;
+ $ret = '';
+ } else {
+ $ret .= $string{$i};
+ }
+ }
+ }
+ break;
+ # end switch
+ }
+ }
+ if ($enc_init) {
+ if ($iEncStart !== false) {
+ $aRet[] = substr($string, $iOffset, $iEncStart-$iOffset);
+ $aRet[] = "=?$default_charset?Q?$ret?=";
+ } else {
+ $aRet[] = substr($string, $iOffset);
+ }
+ $string = implode('', $aRet);
+ }
+ return $string;
+}
+
+
+if (!function_exists('random_int')) { // PHP version < 7.0
+ require_once(dirname(__FILE__) . '/lib/block_random_int.php');
+}
+
+
+/**
+ * Generate a random password of $length characters.
+ * @param int $length (optional, default: 12)
+ * @return string
+ *
+ */
+function generate_password($length = 12) {
+
+ // define possible characters
+ $possible = "2345678923456789abcdefghijkmnpqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ"; # skip 0 and 1 to avoid confusion with O and l
+
+ // add random characters to $password until $length is reached
+ $password = "";
+ while (strlen($password) < $length) {
+ $random = random_int(0, strlen($possible) -1);
+ $char = substr($possible, $random, 1);
+
+ // we don't want this character if it's already in the password
+ if (!strstr($password, $char)) {
+ $password .= $char;
+ }
+ }
+
+ return $password;
+}
+
+
+
+/**
+ * Check if a password is strong enough based on the conditions in $CONF['password_validation']
+ * @param string $password
+ * @return array of error messages, or empty array if the password is ok
+ */
+function validate_password($password) {
+ $result = array();
+ $val_conf = Config::read_array('password_validation');
+
+ $minlen = (int) Config::read_string('min_password_length'); # used up to 2.3.x - check it for backward compatibility
+ if ($minlen > 0) {
+ $val_conf['/.{' . $minlen . '}/'] = "password_too_short $minlen";
+ }
+
+ foreach ($val_conf as $regex => $message) {
+ if (!preg_match($regex, $password)) {
+ $msgparts = preg_split("/ /", $message, 2);
+ if (count($msgparts) == 1) {
+ $result[] = Config::lang($msgparts[0]);
+ } else {
+ $result[] = sprintf(Config::lang($msgparts[0]), $msgparts[1]);
+ }
+ }
+ }
+
+ return $result;
+}
+
+/**
+ * @param string $pw
+ * @param string $pw_db - encrypted hash
+ * @return string crypt'ed password, should equal $pw_db if $pw matches the original
+ */
+function _pacrypt_md5crypt($pw, $pw_db = '') {
+ if ($pw_db) {
+ $split_salt = preg_split('/\$/', $pw_db);
+ if (isset($split_salt[2])) {
+ $salt = $split_salt[2];
+ return md5crypt($pw, $salt);
+ }
+ }
+
+ return md5crypt($pw);
+}
+
+function _pacrypt_crypt($pw, $pw_db = '') {
+ if ($pw_db) {
+ return crypt($pw, $pw_db);
+ }
+ return crypt($pw);
+}
+
+/**
+ * Crypt with MySQL's ENCRYPT function
+ *
+ * @param string $pw
+ * @param string $pw_db (hashed password)
+ * @return string if $pw_db and the return value match then $pw matches the original password.
+ */
+function _pacrypt_mysql_encrypt($pw, $pw_db = '') {
+ // See https://sourceforge.net/tracker/?func=detail&atid=937966&aid=1793352&group_id=191583
+ // this is apparently useful for pam_mysql etc.
+
+ if ( $pw_db ) {
+ $res = db_query_one("SELECT ENCRYPT(:pw,:pw_db) as result", ['pw' => $pw, 'pw_db' => $pw_db]);
+ } else {
+ $res= db_query_one("SELECT ENCRYPT(:pw) as result", ['pw' => $pw]);
+ }
+
+ return $res['result'];
+}
+
+/**
+ * Create/Validate courier authlib style crypt'ed passwords. (md5, md5raw, crypt, sha1)
+ *
+ * @param string $pw
+ * @param string $pw_db (optional)
+ * @return string crypted password - contains {xxx} prefix to identify mechanism.
+ */
+function _pacrypt_authlib($pw, $pw_db) {
+ global $CONF;
+ $flavor = $CONF['authlib_default_flavor'];
+ $salt = substr(create_salt(), 0, 2); # courier-authlib supports only two-character salts
+ if (preg_match('/^{.*}/', $pw_db)) {
+ // we have a flavor in the db -> use it instead of default flavor
+ $result = preg_split('/[{}]/', $pw_db, 3); # split at { and/or }
+ $flavor = $result[1];
+ $salt = substr($result[2], 0, 2);
+ }
+
+ if (stripos($flavor, 'md5raw') === 0) {
+ $password = '{' . $flavor . '}' . md5($pw);
+ } elseif (stripos($flavor, 'md5') === 0) {
+ $password = '{' . $flavor . '}' . base64_encode(md5($pw, true));
+ } elseif (stripos($flavor, 'crypt') === 0) {
+ $password = '{' . $flavor . '}' . crypt($pw, $salt);
+ } elseif (stripos($flavor, 'SHA') === 0) {
+ $password = '{' . $flavor . '}' . base64_encode(sha1($pw, true));
+ } else {
+ die("authlib_default_flavor '" . $flavor . "' unknown. Valid flavors are 'md5raw', 'md5', 'SHA' and 'crypt'");
+ }
+ return $password;
+}
+
+/**
+ * Uses the doveadm pw command, crypted passwords have a {...} prefix to identify type.
+ *
+ * @param string $pw - plain text password
+ * @param string $pw_db - encrypted password, or '' for generation.
+ * @return string crypted password
+ */
+function _pacrypt_dovecot($pw, $pw_db = '') {
+ global $CONF;
+
+ $split_method = preg_split('/:/', $CONF['encrypt']);
+ $method = strtoupper($split_method[1]);
+ # If $pw_db starts with {method}, change $method accordingly
+ if (!empty($pw_db) && preg_match('/^\{([A-Z0-9.-]+)\}.+/', $pw_db, $method_matches)) {
+ $method = $method_matches[1];
+ }
+ if (! preg_match("/^[A-Z0-9.-]+$/", $method)) {
+ die("invalid dovecot encryption method");
+ }
+
+ # TODO: check against a fixed list?
+ # if (strtolower($method) == 'md5-crypt') die("\$CONF['encrypt'] = 'dovecot:md5-crypt' will not work because dovecotpw generates a random salt each time. Please use \$CONF['encrypt'] = 'md5crypt' instead.");
+ # $crypt_method = preg_match ("/.*-CRYPT$/", $method);
+
+ # digest-md5 hashes include the username - until someone implements it, let's declare it as unsupported
+ if (strtolower($method) == 'digest-md5') {
+ die("Sorry, \$CONF['encrypt'] = 'dovecot:digest-md5' is not supported by PostfixAdmin.");
+ }
+ # TODO: add -u option for those hashes, or for everything that is salted (-u was available before dovecot 2.1 -> no problem with backward compatibility )
+
+ $dovecotpw = "doveadm pw";
+ if (!empty($CONF['dovecotpw'])) {
+ $dovecotpw = $CONF['dovecotpw'];
+ }
+
+ # Use proc_open call to avoid safe_mode problems and to prevent showing plain password in process table
+ $spec = array(
+ 0 => array("pipe", "r"), // stdin
+ 1 => array("pipe", "w"), // stdout
+ 2 => array("pipe", "w"), // stderr
+ );
+
+ $nonsaltedtypes = "SHA|SHA1|SHA256|SHA512|CLEAR|CLEARTEXT|PLAIN|PLAIN-TRUNC|CRAM-MD5|HMAC-MD5|PLAIN-MD4|PLAIN-MD5|LDAP-MD5|LANMAN|NTLM|RPA";
+ $salted = ! preg_match("/^($nonsaltedtypes)(\.B64|\.BASE64|\.HEX)?$/", strtoupper($method));
+
+ $dovepasstest = '';
+ if ($salted && (!empty($pw_db))) {
+ # only use -t for salted passwords to be backward compatible with dovecot < 2.1
+ $dovepasstest = " -t " . escapeshellarg($pw_db);
+ }
+ $pipe = proc_open("$dovecotpw '-s' $method$dovepasstest", $spec, $pipes);
+
+ if (!$pipe) {
+ die("can't proc_open $dovecotpw");
+ }
+
+ // use dovecot's stdin, it uses getpass() twice (except when using -t)
+ // Write pass in pipe stdin
+ if (empty($dovepasstest)) {
+ fwrite($pipes[0], $pw . "\n", 1+strlen($pw));
+ usleep(1000);
+ }
+ fwrite($pipes[0], $pw . "\n", 1+strlen($pw));
+ fclose($pipes[0]);
+
+ // Read hash from pipe stdout
+ $password = fread($pipes[1], 200);
+
+ if (empty($dovepasstest)) {
+ if (!preg_match('/^\{' . $method . '\}/', $password)) {
+ $stderr_output = stream_get_contents($pipes[2]);
+ error_log('dovecotpw password encryption failed. STDERR output: '. $stderr_output);
+ die("can't encrypt password with dovecotpw, see error log for details");
+ }
+ } else {
+ if (!preg_match('(verified)', $password)) {
+ $password="Thepasswordcannotbeverified";
+ } else {
+ $password = rtrim(str_replace('(verified)', '', $password));
+ }
+ }
+
+ fclose($pipes[1]);
+ fclose($pipes[2]);
+ proc_close($pipe);
+
+ if ((!empty($pw_db)) && (substr($pw_db, 0, 1) != '{')) {
+ # for backward compability with "old" dovecot passwords that don't have the {method} prefix
+ $password = str_replace('{' . $method . '}', '', $password);
+ }
+
+ return rtrim($password);
+}
+
+/**
+ * Supports DES, MD5, BLOWFISH, SHA256, SHA512 methods.
+ *
+ * @param string $pw
+ * @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.
+ */
+function _pacrypt_php_crypt($pw, $pw_db) {
+ global $CONF;
+
+ // use PHPs crypt(), which uses the system's crypt()
+ // same algorithms as used in /etc/shadow
+ // you can have mixed hash types in the database for authentication, changed passwords get specified hash type
+ // 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:METHOD' to use another method; methods supported: DES, MD5, BLOWFISH, SHA256, SHA512
+ // tested on linux
+
+ if (strlen($pw_db) > 0) {
+ // existing pw provided. send entire password hash as salt for crypt() to figure out
+ $salt = $pw_db;
+ } else {
+ $salt_method = 'SHA512'; // hopefully a reasonable default (better than MD5)
+ $hash_difficulty = '';
+ // no pw provided. create new password hash
+ if (strpos($CONF['encrypt'], ':') !== false) {
+ // use specified hash method
+ $split_method = explode(':', $CONF['encrypt']);
+ $salt_method = $split_method[1];
+ if (count($split_method) >= 3) {
+ $hash_difficulty = $split_method[2];
+ }
+ }
+ // create appropriate salt for selected hash method
+ $salt = _php_crypt_generate_crypt_salt($salt_method, $hash_difficulty);
+ }
+ // send it to PHPs crypt()
+ $password = crypt($pw, $salt);
+ return $password;
+}
+
+/**
+ * @param string $hash_type must be one of: MD5, DES, BLOWFISH, SHA256 or SHA512 (default)
+ * @param int hash difficulty
+ * @return string
+ */
+function _php_crypt_generate_crypt_salt($hash_type='SHA512', $hash_difficulty=null) {
+ // generate a salt (with magic matching chosen hash algorithm) for the PHP crypt() function
+
+ // most commonly used alphabet
+ $alphabet = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+
+ switch ($hash_type) {
+ case 'DES':
+ $alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+ $length = 2;
+ $salt = _php_crypt_random_string($alphabet, $length);
+ return $salt;
+
+ case 'MD5':
+ $length = 12;
+ $algorithm = '1';
+ $salt = _php_crypt_random_string($alphabet, $length);
+ return sprintf('$%s$%s', $algorithm, $salt);
+
+ case 'BLOWFISH':
+ $length = 22;
+ if (empty($hash_difficulty)) {
+ $cost = 10;
+ } else {
+ $cost = (int)$hash_difficulty;
+ if ($cost < 4 || $cost > 31) {
+ die('invalid encrypt difficulty setting "' . $hash_difficulty . '" for ' . $hash_type . ', the valid range is 4-31');
+ }
+ }
+ if (version_compare(PHP_VERSION, '5.3.7') >= 0) {
+ $algorithm = '2y'; // bcrypt, with fixed unicode problem
+ } else {
+ $algorithm = '2a'; // bcrypt
+ }
+ $salt = _php_crypt_random_string($alphabet, $length);
+ return sprintf('$%s$%02d$%s', $algorithm, $cost, $salt);
+
+ case 'SHA256':
+ $length = 16;
+ $algorithm = '5';
+ if (empty($hash_difficulty)) {
+ $rounds = '';
+ } else {
+ $rounds = (int)$hash_difficulty;
+ if ($rounds < 1000 || $rounds > 999999999) {
+ die('invalid encrypt difficulty setting "' . $hash_difficulty . '" for ' . $hash_type . ', the valid range is 1000-999999999');
+ }
+ }
+ $salt = _php_crypt_random_string($alphabet, $length);
+ if (!empty($rounds)) {
+ $rounds = sprintf('rounds=%d$', $rounds);
+ }
+ return sprintf('$%s$%s%s', $algorithm, $rounds, $salt);
+
+ case 'SHA512':
+ $length = 16;
+ $algorithm = '6';
+ if (empty($hash_difficulty)) {
+ $rounds = '';
+ } else {
+ $rounds = (int)$hash_difficulty;
+ if ($rounds < 1000 || $rounds > 999999999) {
+ die('invalid encrypt difficulty setting "' . $hash_difficulty . '" for ' . $hash_type . ', the valid range is 1000-999999999');
+ }
+ }
+ $salt = _php_crypt_random_string($alphabet, $length);
+ if (!empty($rounds)) {
+ $rounds = sprintf('rounds=%d$', $rounds);
+ }
+ return sprintf('$%s$%s%s', $algorithm, $rounds, $salt);
+
+ default:
+ die("unknown hash type: '$hash_type'");
+ }
+}
+
+/**
+ * Generates a random string of specified $length from $characters.
+ * @param string $characters
+ * @param int $length
+ * @return string of given $length
+ */
+function _php_crypt_random_string($characters, $length) {
+ $string = '';
+ for ($p = 0; $p < $length; $p++) {
+ $string .= $characters[random_int(0, strlen($characters) -1)];
+ }
+ return $string;
+}
+
+
+/**
+ * Encrypt a password, using the apparopriate hashing mechanism as defined in
+ * config.inc.php ($CONF['encrypt']).
+ *
+ * When wanting to compare one pw to another, it's necessary to provide the salt used - hence
+ * the second parameter ($pw_db), which is the existing hash from the DB.
+ *
+ * @param string $pw
+ * @param string $pw_db optional encrypted password
+ * @return string encrypted password - if this matches $pw_db then the original password is $pw.
+ */
+function pacrypt($pw, $pw_db="") {
+ global $CONF;
+
+ switch ($CONF['encrypt']) {
+ case 'md5crypt':
+ return _pacrypt_md5crypt($pw, $pw_db);
+ case 'md5':
+ return md5($pw);
+ case 'system':
+ return _pacrypt_crypt($pw, $pw_db);
+ case 'cleartext':
+ return $pw;
+ case 'mysql_encrypt':
+ return _pacrypt_mysql_encrypt($pw, $pw_db);
+ case 'authlib':
+ return _pacrypt_authlib($pw, $pw_db);
+ }
+
+ if (preg_match("/^dovecot:/", $CONF['encrypt'])) {
+ return _pacrypt_dovecot($pw, $pw_db);
+ }
+
+ if (substr($CONF['encrypt'], 0, 9) === 'php_crypt') {
+ return _pacrypt_php_crypt($pw, $pw_db);
+ }
+
+ die('unknown/invalid $CONF["encrypt"] setting: ' . $CONF['encrypt']);
+}
+
+/**
+ * Creates MD5 based crypt formatted password.
+ * If salt is not provided we generate one.
+ *
+ * @param string $pw plain text password
+ * @param string $salt (optional)
+ * @param string $magic (optional)
+ * @return string hashed password in crypt format.
+ */
+function md5crypt($pw, $salt="", $magic="") {
+ $MAGIC = "$1$";
+
+ if ($magic == "") {
+ $magic = $MAGIC;
+ }
+ if ($salt == "") {
+ $salt = create_salt();
+ }
+ $slist = explode("$", $salt);
+ if ($slist[0] == "1") {
+ $salt = $slist[1];
+ }
+
+ $salt = substr($salt, 0, 8);
+ $ctx = $pw . $magic . $salt;
+ $final = hex2bin(md5($pw . $salt . $pw));
+
+ for ($i=strlen($pw); $i>0; $i-=16) {
+ if ($i > 16) {
+ $ctx .= substr($final, 0, 16);
+ } else {
+ $ctx .= substr($final, 0, $i);
+ }
+ }
+ $i = strlen($pw);
+
+ while ($i > 0) {
+ if ($i & 1) {
+ $ctx .= chr(0);
+ } else {
+ $ctx .= $pw[0];
+ }
+ $i = $i >> 1;
+ }
+ $final = hex2bin(md5($ctx));
+
+ for ($i=0;$i<1000;$i++) {
+ $ctx1 = "";
+ if ($i & 1) {
+ $ctx1 .= $pw;
+ } else {
+ $ctx1 .= substr($final, 0, 16);
+ }
+ if ($i % 3) {
+ $ctx1 .= $salt;
+ }
+ if ($i % 7) {
+ $ctx1 .= $pw;
+ }
+ if ($i & 1) {
+ $ctx1 .= substr($final, 0, 16);
+ } else {
+ $ctx1 .= $pw;
+ }
+ $final = hex2bin(md5($ctx1));
+ }
+ $passwd = "";
+ $passwd .= to64(((ord($final[0]) << 16) | (ord($final[6]) << 8) | (ord($final[12]))), 4);
+ $passwd .= to64(((ord($final[1]) << 16) | (ord($final[7]) << 8) | (ord($final[13]))), 4);
+ $passwd .= to64(((ord($final[2]) << 16) | (ord($final[8]) << 8) | (ord($final[14]))), 4);
+ $passwd .= to64(((ord($final[3]) << 16) | (ord($final[9]) << 8) | (ord($final[15]))), 4);
+ $passwd .= to64(((ord($final[4]) << 16) | (ord($final[10]) << 8) | (ord($final[5]))), 4);
+ $passwd .= to64(ord($final[11]), 2);
+ return "$magic$salt\$$passwd";
+}
+
+/**
+ * @return string - should be random, 8 chars long
+ */
+function create_salt() {
+ srand((int) microtime()*1000000);
+ $salt = substr(md5("" . rand(0, 9999999)), 0, 8);
+ return $salt;
+}
+
+/*
+ * remove item $item from array $array
+ */
+function remove_from_array($array, $item) {
+ # array_diff might be faster, but doesn't provide an easy way to know if the value was found or not
+ # return array_diff($array, array($item));
+ $ret = array_search($item, $array);
+ if ($ret === false) {
+ $found = 0;
+ } else {
+ $found = 1;
+ unset($array[$ret]);
+ }
+ return array($found, $array);
+}
+
+function to64($v, $n) {
+ $ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ $ret = "";
+ while (($n - 1) >= 0) {
+ $n--;
+ $ret .= $ITOA64[$v & 0x3f];
+ $v = $v >> 6;
+ }
+ return $ret;
+}
+
+
+
+/**
+ * smtp_mail
+ * Action: Send email
+ * Call: smtp_mail (string to, string from, string subject, string body]) - or -
+ * Call: smtp_mail (string to, string from, string data) - DEPRECATED
+ * @param String - To:
+ * @param String - From:
+ * @param String - Subject: (if called with 4 parameters) or full mail body (if called with 3 parameters)
+ * @param String (optional) - Password
+ * @param String (optional, but recommended) - mail body
+ * @return bool - true on success, otherwise false
+ * TODO: Replace this with something decent like PEAR::Mail or Zend_Mail.
+ */
+function smtp_mail($to, $from, $data, $password = "", $body = "") {
+ global $CONF;
+ $smtpd_server = $CONF['smtp_server'];
+ $smtpd_port = $CONF['smtp_port'];
+ //$smtp_server = $_SERVER["SERVER_NAME"];
+ $smtp_server = php_uname('n');
+ if (!empty($CONF['smtp_client'])) {
+ $smtp_server = $CONF['smtp_client'];
+ }
+ $errno = 0;
+ $errstr = "0";
+ $timeout = 30;
+
+ if ($body != "") {
+ $maildata =
+ "To: " . $to . "\n"
+ . "From: " . $from . "\n"
+ . "Subject: " . encode_header($data) . "\n"
+ . "MIME-Version: 1.0\n"
+ . "Date: " . date('r') . "\n"
+ . "Content-Type: text/plain; charset=utf-8\n"
+ . "Content-Transfer-Encoding: 8bit\n"
+ . "\n"
+ . $body
+ ;
+ } else {
+ $maildata = $data;
+ }
+
+ $fh = @fsockopen($smtpd_server, $smtpd_port, $errno, $errstr, $timeout);
+
+ if (!$fh) {
+ error_log("fsockopen failed - errno: $errno - errstr: $errstr");
+ return false;
+ } else {
+ smtp_get_response($fh);
+ fputs($fh, "EHLO $smtp_server\r\n");
+ smtp_get_response($fh);
+
+ if (!empty($password)) {
+ fputs($fh,"AUTH LOGIN\r\n");
+ smtp_get_response($fh);
+ fputs($fh, base64_encode($from) . "\r\n");
+ smtp_get_response($fh);
+ fputs($fh, base64_encode($password) . "\r\n");
+ smtp_get_response($fh);
+ }
+
+ fputs($fh, "MAIL FROM:<$from>\r\n");
+ smtp_get_response($fh);
+ fputs($fh, "RCPT TO:<$to>\r\n");
+ smtp_get_response($fh);
+ fputs($fh, "DATA\r\n");
+ smtp_get_response($fh);
+ fputs($fh, "$maildata\r\n.\r\n");
+ smtp_get_response($fh);
+ fputs($fh, "QUIT\r\n");
+ smtp_get_response($fh);
+ fclose($fh);
+ }
+ return true;
+}
+
+/**
+ * smtp_get_admin_email
+ * Action: Get configured email address or current user if nothing configured
+ * Call: smtp_get_admin_email
+ * @return string - username/mail address
+ */
+function smtp_get_admin_email() {
+ $admin_email = Config::read_string('admin_email');
+ if (!empty($admin_email)) {
+ return $admin_email;
+ } else {
+ return authentication_get_username();
+ }
+}
+
+/**
+ * smtp_get_admin_password
+ * Action: Get smtp password for admin email
+ * Call: smtp_get_admin_password
+ * @return string - admin smtp password
+ */
+function smtp_get_admin_password() {
+ return Config::read_string('admin_smtp_password');
+}
+
+
+//
+// smtp_get_response
+// Action: Get response from mail server
+// Call: smtp_get_response (string FileHandle)
+//
+function smtp_get_response($fh) {
+ $res ='';
+ do {
+ $line = fgets($fh, 256);
+ $res .= $line;
+ } while (preg_match("/^\d\d\d\-/", $line));
+ return $res;
+}
+
+
+
+$DEBUG_TEXT = <<
FATAL Error:
Invalid \$CONF['database_type']! Please fix your config.inc.php!
- * $sql = "SELECT * FROM foo WHERE x = '" . escape_string('fish') . "'";
- *
- *
- * @param int|string $string parameters to escape
- * @return string cleaned data, suitable for use within an SQL statement.
- */
-function escape_string($string_or_int) {
- $link = db_connect();
- $string_or_int = (string) $string_or_int;
- $quoted = $link->quote($string_or_int);
- return trim($quoted, "'");
-}
-
-
-/**
- * safeget
- * Action: get value from $_GET[$param], or $default if $_GET[$param] is not set
- * Call: $param = safeget('param') # replaces $param = $_GET['param']
- * - or -
- * $param = safeget('param', 'default')
- *
- * @param string $param parameter name.
- * @param string $default (optional) - default value if key is not set.
- * @return string
- */
-function safeget($param, $default = "") {
- $retval = $default;
- if (isset($_GET[$param])) {
- $retval = $_GET[$param];
- }
- return $retval;
-}
-
-/**
- * safepost - similar to safeget() but for $_POST
- * @see safeget()
- * @param string $param parameter name
- * @param string $default (optional) default value (defaults to "")
- * @return string|array - value in $_POST[$param] or $default
- */
-function safepost($param, $default = "") {
- $retval = $default;
- if (isset($_POST[$param])) {
- $retval = $_POST[$param];
- }
- return $retval;
-}
-
-/**
- * safeserver
- * @see safeget()
- * @param string $param
- * @param string $default (optional)
- * @return string value from $_SERVER[$param] or $default
- */
-function safeserver($param, $default = "") {
- $retval = $default;
- if (isset($_SERVER[$param])) {
- $retval = $_SERVER[$param];
- }
- return $retval;
-}
-
-/**
- * safecookie
- * @see safeget()
- * @param string $param
- * @param string $default (optional)
- * @return string value from $_COOKIE[$param] or $default
- */
-function safecookie($param, $default = "") {
- $retval = $default;
- if (isset($_COOKIE[$param])) {
- $retval = $_COOKIE[$param];
- }
- return $retval;
-}
-
-/**
- * safesession
- * @see safeget()
- * @param string $param
- * @param string $default (optional)
- * @return string value from $_SESSION[$param] or $default
- */
-function safesession($param, $default = "") {
- $retval = $default;
- if (isset($_SESSION[$param])) {
- $retval = $_SESSION[$param];
- }
- return $retval;
-}
-
-
-/**
- * pacol
- * @param int $allow_editing
- * @param int $display_in_form
- * @param int display_in_list
- * @param string $type
- * @param string PALANG_label
- * @param string PALANG_desc
- * @param any optional $default
- * @param array $options optional options
- * @param int or $not_in_db - if array, can contain the remaining parameters as associated array. Otherwise counts as $not_in_db
- * @return array for $struct
- */
-function pacol($allow_editing, $display_in_form, $display_in_list, $type, $PALANG_label, $PALANG_desc, $default = "", $options = array(), $multiopt=0, $dont_write_to_db=0, $select="", $extrafrom="", $linkto="") {
- if ($PALANG_label != '') {
- $PALANG_label = Config::lang($PALANG_label);
- }
- if ($PALANG_desc != '') {
- $PALANG_desc = Config::lang($PALANG_desc);
- }
-
- if (is_array($multiopt)) { # remaining parameters provided in named array
- $not_in_db = 0; # keep default value
- foreach ($multiopt as $key => $value) {
- $$key = $value; # extract everything to the matching variable
- }
- } else {
- $not_in_db = $multiopt;
- }
-
- return array(
- 'editable' => $allow_editing,
- 'display_in_form' => $display_in_form,
- 'display_in_list' => $display_in_list,
- 'type' => $type,
- 'label' => $PALANG_label, # $PALANG field label
- 'desc' => $PALANG_desc, # $PALANG field description
- 'default' => $default,
- 'options' => $options,
- 'not_in_db' => $not_in_db,
- 'dont_write_to_db' => $dont_write_to_db,
- 'select' => $select, # replaces the field name after SELECT
- 'extrafrom' => $extrafrom, # added after FROM xy - useful for JOINs etc.
- 'linkto' => $linkto, # make the value a link - %s will be replaced with the ID
- );
-}
-
-/**
- * Action: Get all the properties of a domain.
- * @param string $domain
- * @return array
- */
-function get_domain_properties($domain) {
- $handler = new DomainHandler();
- if (!$handler->init($domain)) {
- die("Error: " . join("\n", $handler->errormsg));
- }
-
- if (!$handler->view()) {
- die("Error: " . join("\n", $handler->errormsg));
- }
-
- $result = $handler->result();
- return $result;
-}
-
-
-/**
- * create_page_browser
- * Action: Get page browser for a long list of mailboxes, aliases etc.
- * Call: $pagebrowser = create_page_browser('table.field', 'query', 50) # replaces $param = $_GET['param']
- *
- * @param string $idxfield - database field name to use as title
- * @param string $querypart - core part of the query (starting at "FROM")
- * @return array
- */
-function create_page_browser($idxfield, $querypart, $sql_params = []) {
- global $CONF;
- $page_size = (int) $CONF['page_size'];
- $label_len = 2;
- $pagebrowser = array();
-
- $count_results = 0;
-
- if ($page_size < 2) { # will break the page browser
- die('$CONF[\'page_size\'] must be 2 or more!');
- }
-
- # get number of rows
- $query = "SELECT count(*) as counter FROM (SELECT $idxfield $querypart) AS tmp";
- $result = db_query_one($query, $sql_params);
- if ($result && isset($result['counter'])) {
- $count_results = $result['counter'] -1; # we start counting at 0, not 1
- }
-
- if ($count_results < $page_size) {
- return array(); # only one page - no pagebrowser required
- }
-
- # init row counter
- $initcount = "SET @r=-1";
- if (db_pgsql()) {
- $initcount = "CREATE TEMPORARY SEQUENCE rowcount MINVALUE 0";
- }
- if (!db_sqlite()) {
- db_execute($initcount);
- }
-
- # get labels for relevant rows (first and last of each page)
- $page_size_zerobase = $page_size - 1;
- $query = "
- SELECT * FROM (
- SELECT $idxfield AS label, @r := @r + 1 AS 'r' $querypart
- ) idx WHERE MOD(idx.r, $page_size) IN (0,$page_size_zerobase) OR idx.r = $count_results
- ";
-
- if (db_pgsql()) {
- $query = "
- SELECT * FROM (
- SELECT $idxfield AS label, nextval('rowcount') AS row $querypart
- ) idx WHERE MOD(idx.row, $page_size) IN (0,$page_size_zerobase) OR idx.row = $count_results
- ";
- }
-
- if (db_sqlite()) {
- $query = "
- WITH idx AS (SELECT * $querypart)
- SELECT $idxfield AS label, (SELECT (COUNT(*) - 1) FROM idx t1 WHERE t1.$idxfield <= t2.$idxfield) AS row
- FROM idx t2
- WHERE (row % $page_size) IN (0,$page_size_zerobase) OR row = $count_results";
- }
-
- # PostgreSQL:
- # http://www.postgresql.org/docs/8.1/static/sql-createsequence.html
- # http://www.postgresonline.com/journal/archives/79-Simulating-Row-Number-in-PostgreSQL-Pre-8.4.html
- # http://www.pg-forum.de/sql/1518-nummerierung-der-abfrageergebnisse.html
- # CREATE TEMPORARY SEQUENCE foo MINVALUE 0 MAXVALUE $page_size_zerobase CYCLE
- # afterwards: DROP SEQUENCE foo
-
- $result = db_query_all($query, $sql_params);
- foreach ($result as $k => $row) {
- if (isset($result[$k + 1])) {
- $row2 = $result[$k + 1];
- $label = substr($row['label'], 0, $label_len) . '-' . substr($row2['label'], 0, $label_len);
- } else {
- $label = substr($row['label'], 0, $label_len);
- }
- $pagebrowser[] = $label;
- }
-
- if (db_pgsql()) {
- db_execute("DROP SEQUENCE rowcount");
- }
-
- return $pagebrowser;
-}
-
-
-/**
- * Recalculates the quota from MBs to bytes (divide, /)
- * @param int $quota
- * @return float
- */
-function divide_quota($quota) {
- if ($quota == -1) {
- return $quota;
- }
- $value = round($quota / (int) Config::read_string('quota_multiplier'), 2);
- return $value;
-}
-
-
-/**
- * Checks if the admin is the owner of the domain (or global-admin)
- * @param string $username
- * @param string $domain
- * @return bool
- */
-function check_owner($username, $domain) {
- $table_domain_admins = table_by_key('domain_admins');
-
- $result = db_query_all(
- "SELECT 1 FROM $table_domain_admins WHERE username= ? AND (domain = ? OR domain = 'ALL') AND active = ?" ,
- array($username, $domain, db_get_boolean(true))
- );
-
- if (sizeof($result) == 1 || sizeof($result) == 2) { # "ALL" + specific domain permissions is possible
- # TODO: if superadmin, check if given domain exists in the database
- return true;
- } else {
- if (sizeof($result) > 2) { # more than 2 results means something really strange happened...
- flash_error("Permission check returned multiple results. Please go to 'edit admin' for your username and press the save "
- . "button once to fix the database. If this doesn't help, open a bugreport.");
- }
- return false;
- }
-}
-
-
-
-/**
- * List domains for an admin user.
- * @param String $username
- * @return array of domain names.
- */
-function list_domains_for_admin($username) {
- $table_domain = table_by_key('domain');
- $table_domain_admins = table_by_key('domain_admins');
-
- $condition = array();
-
- $E_username = escape_string($username);
-
- $query = "SELECT $table_domain.domain FROM $table_domain ";
- $condition[] = "$table_domain.domain != 'ALL'";
-
- $pvalues = array();
-
- $result = db_query_one("SELECT username FROM $table_domain_admins WHERE username= :username AND domain='ALL'", array('username' => $username));
- if (empty($result)) { # not a superadmin
- $pvalues['username'] = $username;
- $pvalues['active'] = db_get_boolean(true);
- $pvalues['backupmx'] = db_get_boolean(false);
-
- $query .= " LEFT JOIN $table_domain_admins ON $table_domain.domain=$table_domain_admins.domain ";
- $condition[] = "$table_domain_admins.username = :username ";
- $condition[] = "$table_domain.active = :active "; # TODO: does it really make sense to exclude inactive...
- $condition[] = "$table_domain.backupmx = :backupmx" ; # TODO: ... and backupmx domains for non-superadmins?
- }
-
- $query .= " WHERE " . join(' AND ', $condition);
- $query .= " ORDER BY $table_domain.domain";
-
- $result = db_query_all($query, $pvalues);
-
- return array_column($result, 'domain');
-}
-
-
-if (!function_exists('array_column')) {
- require_once(dirname(__FILE__) . '/lib/array_column.php');
-}
-
-/**
- * List all available domains.
- *
- * @return array
- */
-function list_domains() {
- $list = array();
-
- $table_domain = table_by_key('domain');
- $result = db_query_all("SELECT domain FROM $table_domain WHERE domain!='ALL' ORDER BY domain");
- $i = 0;
- foreach ($result as $row) {
- $list[$i] = $row['domain'];
- $i++;
- }
- return $list;
-}
-
-
-
-
-//
-// list_admins
-// Action: Lists all the admins
-// Call: list_admins ()
-//
-// was admin_list_admins
-//
-function list_admins() {
- $handler = new AdminHandler();
-
- $handler->getList('');
-
- return $handler->result();
-}
-
-
-
-//
-// encode_header
-// Action: Encode a string according to RFC 1522 for use in headers if it contains 8-bit characters.
-// Call: encode_header (string header, string charset)
-//
-function encode_header($string, $default_charset = "utf-8") {
- if (strtolower($default_charset) == 'iso-8859-1') {
- $string = str_replace("\240", ' ', $string);
- }
-
- $j = strlen($string);
- $max_l = 75 - strlen($default_charset) - 7;
- $aRet = array();
- $ret = '';
- $iEncStart = $enc_init = false;
- $cur_l = $iOffset = 0;
-
- for ($i = 0; $i < $j; ++$i) {
- switch ($string{$i}) {
- case '=':
- case '<':
- case '>':
- case ',':
- case '?':
- case '_':
- if ($iEncStart === false) {
- $iEncStart = $i;
- }
- $cur_l+=3;
- if ($cur_l > ($max_l-2)) {
- $aRet[] = substr($string, $iOffset, $iEncStart-$iOffset);
- $aRet[] = "=?$default_charset?Q?$ret?=";
- $iOffset = $i;
- $cur_l = 0;
- $ret = '';
- $iEncStart = false;
- } else {
- $ret .= sprintf("=%02X", ord($string{$i}));
- }
- break;
- case '(':
- case ')':
- if ($iEncStart !== false) {
- $aRet[] = substr($string, $iOffset, $iEncStart-$iOffset);
- $aRet[] = "=?$default_charset?Q?$ret?=";
- $iOffset = $i;
- $cur_l = 0;
- $ret = '';
- $iEncStart = false;
- }
- break;
- case ' ':
- if ($iEncStart !== false) {
- $cur_l++;
- if ($cur_l > $max_l) {
- $aRet[] = substr($string, $iOffset, $iEncStart-$iOffset);
- $aRet[] = "=?$default_charset?Q?$ret?=";
- $iOffset = $i;
- $cur_l = 0;
- $ret = '';
- $iEncStart = false;
- } else {
- $ret .= '_';
- }
- }
- break;
- default:
- $k = ord($string{$i});
- if ($k > 126) {
- if ($iEncStart === false) {
- // do not start encoding in the middle of a string, also take the rest of the word.
- $sLeadString = substr($string, 0, $i);
- $aLeadString = explode(' ', $sLeadString);
- $sToBeEncoded = array_pop($aLeadString);
- $iEncStart = $i - strlen($sToBeEncoded);
- $ret .= $sToBeEncoded;
- $cur_l += strlen($sToBeEncoded);
- }
- $cur_l += 3;
- // first we add the encoded string that reached it's max size
- if ($cur_l > ($max_l-2)) {
- $aRet[] = substr($string, $iOffset, $iEncStart-$iOffset);
- $aRet[] = "=?$default_charset?Q?$ret?= ";
- $cur_l = 3;
- $ret = '';
- $iOffset = $i;
- $iEncStart = $i;
- }
- $enc_init = true;
- $ret .= sprintf("=%02X", $k);
- } else {
- if ($iEncStart !== false) {
- $cur_l++;
- if ($cur_l > $max_l) {
- $aRet[] = substr($string, $iOffset, $iEncStart-$iOffset);
- $aRet[] = "=?$default_charset?Q?$ret?=";
- $iEncStart = false;
- $iOffset = $i;
- $cur_l = 0;
- $ret = '';
- } else {
- $ret .= $string{$i};
- }
- }
- }
- break;
- # end switch
- }
- }
- if ($enc_init) {
- if ($iEncStart !== false) {
- $aRet[] = substr($string, $iOffset, $iEncStart-$iOffset);
- $aRet[] = "=?$default_charset?Q?$ret?=";
- } else {
- $aRet[] = substr($string, $iOffset);
- }
- $string = implode('', $aRet);
- }
- return $string;
-}
-
-
-if (!function_exists('random_int')) { // PHP version < 7.0
- require_once(dirname(__FILE__) . '/lib/block_random_int.php');
-}
-
-
-/**
- * Generate a random password of $length characters.
- * @param int $length (optional, default: 12)
- * @return string
- *
- */
-function generate_password($length = 12) {
-
- // define possible characters
- $possible = "2345678923456789abcdefghijkmnpqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ"; # skip 0 and 1 to avoid confusion with O and l
-
- // add random characters to $password until $length is reached
- $password = "";
- while (strlen($password) < $length) {
- $random = random_int(0, strlen($possible) -1);
- $char = substr($possible, $random, 1);
-
- // we don't want this character if it's already in the password
- if (!strstr($password, $char)) {
- $password .= $char;
- }
- }
-
- return $password;
-}
-
-
-
-/**
- * Check if a password is strong enough based on the conditions in $CONF['password_validation']
- * @param string $password
- * @return array of error messages, or empty array if the password is ok
- */
-function validate_password($password) {
- $result = array();
- $val_conf = Config::read_array('password_validation');
-
- $minlen = (int) Config::read_string('min_password_length'); # used up to 2.3.x - check it for backward compatibility
- if ($minlen > 0) {
- $val_conf['/.{' . $minlen . '}/'] = "password_too_short $minlen";
- }
-
- foreach ($val_conf as $regex => $message) {
- if (!preg_match($regex, $password)) {
- $msgparts = preg_split("/ /", $message, 2);
- if (count($msgparts) == 1) {
- $result[] = Config::lang($msgparts[0]);
- } else {
- $result[] = sprintf(Config::lang($msgparts[0]), $msgparts[1]);
- }
- }
- }
-
- return $result;
-}
-
-/**
- * @param string $pw
- * @param string $pw_db - encrypted hash
- * @return string crypt'ed password, should equal $pw_db if $pw matches the original
- */
-function _pacrypt_md5crypt($pw, $pw_db = '') {
- if ($pw_db) {
- $split_salt = preg_split('/\$/', $pw_db);
- if (isset($split_salt[2])) {
- $salt = $split_salt[2];
- return md5crypt($pw, $salt);
- }
- }
-
- return md5crypt($pw);
-}
-
-function _pacrypt_crypt($pw, $pw_db = '') {
- if ($pw_db) {
- return crypt($pw, $pw_db);
- }
- return crypt($pw);
-}
-
-/**
- * Crypt with MySQL's ENCRYPT function
- *
- * @param string $pw
- * @param string $pw_db (hashed password)
- * @return string if $pw_db and the return value match then $pw matches the original password.
- */
-function _pacrypt_mysql_encrypt($pw, $pw_db = '') {
- // See https://sourceforge.net/tracker/?func=detail&atid=937966&aid=1793352&group_id=191583
- // this is apparently useful for pam_mysql etc.
-
- if ( $pw_db ) {
- $res = db_query_one("SELECT ENCRYPT(:pw,:pw_db) as result", ['pw' => $pw, 'pw_db' => $pw_db]);
- } else {
- $res= db_query_one("SELECT ENCRYPT(:pw) as result", ['pw' => $pw]);
- }
-
- return $res['result'];
-}
-
-/**
- * Create/Validate courier authlib style crypt'ed passwords. (md5, md5raw, crypt, sha1)
- *
- * @param string $pw
- * @param string $pw_db (optional)
- * @return string crypted password - contains {xxx} prefix to identify mechanism.
- */
-function _pacrypt_authlib($pw, $pw_db) {
- global $CONF;
- $flavor = $CONF['authlib_default_flavor'];
- $salt = substr(create_salt(), 0, 2); # courier-authlib supports only two-character salts
- if (preg_match('/^{.*}/', $pw_db)) {
- // we have a flavor in the db -> use it instead of default flavor
- $result = preg_split('/[{}]/', $pw_db, 3); # split at { and/or }
- $flavor = $result[1];
- $salt = substr($result[2], 0, 2);
- }
-
- if (stripos($flavor, 'md5raw') === 0) {
- $password = '{' . $flavor . '}' . md5($pw);
- } elseif (stripos($flavor, 'md5') === 0) {
- $password = '{' . $flavor . '}' . base64_encode(md5($pw, true));
- } elseif (stripos($flavor, 'crypt') === 0) {
- $password = '{' . $flavor . '}' . crypt($pw, $salt);
- } elseif (stripos($flavor, 'SHA') === 0) {
- $password = '{' . $flavor . '}' . base64_encode(sha1($pw, true));
- } else {
- die("authlib_default_flavor '" . $flavor . "' unknown. Valid flavors are 'md5raw', 'md5', 'SHA' and 'crypt'");
- }
- return $password;
-}
-
-/**
- * Uses the doveadm pw command, crypted passwords have a {...} prefix to identify type.
- *
- * @param string $pw - plain text password
- * @param string $pw_db - encrypted password, or '' for generation.
- * @return string crypted password
- */
-function _pacrypt_dovecot($pw, $pw_db = '') {
- global $CONF;
-
- $split_method = preg_split('/:/', $CONF['encrypt']);
- $method = strtoupper($split_method[1]);
- # If $pw_db starts with {method}, change $method accordingly
- if (!empty($pw_db) && preg_match('/^\{([A-Z0-9.-]+)\}.+/', $pw_db, $method_matches)) {
- $method = $method_matches[1];
- }
- if (! preg_match("/^[A-Z0-9.-]+$/", $method)) {
- die("invalid dovecot encryption method");
- }
-
- # TODO: check against a fixed list?
- # if (strtolower($method) == 'md5-crypt') die("\$CONF['encrypt'] = 'dovecot:md5-crypt' will not work because dovecotpw generates a random salt each time. Please use \$CONF['encrypt'] = 'md5crypt' instead.");
- # $crypt_method = preg_match ("/.*-CRYPT$/", $method);
-
- # digest-md5 hashes include the username - until someone implements it, let's declare it as unsupported
- if (strtolower($method) == 'digest-md5') {
- die("Sorry, \$CONF['encrypt'] = 'dovecot:digest-md5' is not supported by PostfixAdmin.");
- }
- # TODO: add -u option for those hashes, or for everything that is salted (-u was available before dovecot 2.1 -> no problem with backward compatibility )
-
- $dovecotpw = "doveadm pw";
- if (!empty($CONF['dovecotpw'])) {
- $dovecotpw = $CONF['dovecotpw'];
- }
-
- # Use proc_open call to avoid safe_mode problems and to prevent showing plain password in process table
- $spec = array(
- 0 => array("pipe", "r"), // stdin
- 1 => array("pipe", "w"), // stdout
- 2 => array("pipe", "w"), // stderr
- );
-
- $nonsaltedtypes = "SHA|SHA1|SHA256|SHA512|CLEAR|CLEARTEXT|PLAIN|PLAIN-TRUNC|CRAM-MD5|HMAC-MD5|PLAIN-MD4|PLAIN-MD5|LDAP-MD5|LANMAN|NTLM|RPA";
- $salted = ! preg_match("/^($nonsaltedtypes)(\.B64|\.BASE64|\.HEX)?$/", strtoupper($method));
-
- $dovepasstest = '';
- if ($salted && (!empty($pw_db))) {
- # only use -t for salted passwords to be backward compatible with dovecot < 2.1
- $dovepasstest = " -t " . escapeshellarg($pw_db);
- }
- $pipe = proc_open("$dovecotpw '-s' $method$dovepasstest", $spec, $pipes);
-
- if (!$pipe) {
- die("can't proc_open $dovecotpw");
- }
-
- // use dovecot's stdin, it uses getpass() twice (except when using -t)
- // Write pass in pipe stdin
- if (empty($dovepasstest)) {
- fwrite($pipes[0], $pw . "\n", 1+strlen($pw));
- usleep(1000);
- }
- fwrite($pipes[0], $pw . "\n", 1+strlen($pw));
- fclose($pipes[0]);
-
- // Read hash from pipe stdout
- $password = fread($pipes[1], 200);
-
- if (empty($dovepasstest)) {
- if (!preg_match('/^\{' . $method . '\}/', $password)) {
- $stderr_output = stream_get_contents($pipes[2]);
- error_log('dovecotpw password encryption failed. STDERR output: '. $stderr_output);
- die("can't encrypt password with dovecotpw, see error log for details");
- }
- } else {
- if (!preg_match('(verified)', $password)) {
- $password="Thepasswordcannotbeverified";
- } else {
- $password = rtrim(str_replace('(verified)', '', $password));
- }
- }
-
- fclose($pipes[1]);
- fclose($pipes[2]);
- proc_close($pipe);
-
- if ((!empty($pw_db)) && (substr($pw_db, 0, 1) != '{')) {
- # for backward compability with "old" dovecot passwords that don't have the {method} prefix
- $password = str_replace('{' . $method . '}', '', $password);
- }
-
- return rtrim($password);
-}
-
-/**
- * Supports DES, MD5, BLOWFISH, SHA256, SHA512 methods.
- *
- * @param string $pw
- * @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.
- */
-function _pacrypt_php_crypt($pw, $pw_db) {
- global $CONF;
-
- // use PHPs crypt(), which uses the system's crypt()
- // same algorithms as used in /etc/shadow
- // you can have mixed hash types in the database for authentication, changed passwords get specified hash type
- // 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:METHOD' to use another method; methods supported: DES, MD5, BLOWFISH, SHA256, SHA512
- // tested on linux
-
- if (strlen($pw_db) > 0) {
- // existing pw provided. send entire password hash as salt for crypt() to figure out
- $salt = $pw_db;
- } else {
- $salt_method = 'SHA512'; // hopefully a reasonable default (better than MD5)
- $hash_difficulty = '';
- // no pw provided. create new password hash
- if (strpos($CONF['encrypt'], ':') !== false) {
- // use specified hash method
- $split_method = explode(':', $CONF['encrypt']);
- $salt_method = $split_method[1];
- if (count($split_method) >= 3) {
- $hash_difficulty = $split_method[2];
- }
- }
- // create appropriate salt for selected hash method
- $salt = _php_crypt_generate_crypt_salt($salt_method, $hash_difficulty);
- }
- // send it to PHPs crypt()
- $password = crypt($pw, $salt);
- return $password;
-}
-
-/**
- * @param string $hash_type must be one of: MD5, DES, BLOWFISH, SHA256 or SHA512 (default)
- * @param int hash difficulty
- * @return string
- */
-function _php_crypt_generate_crypt_salt($hash_type='SHA512', $hash_difficulty=null) {
- // generate a salt (with magic matching chosen hash algorithm) for the PHP crypt() function
-
- // most commonly used alphabet
- $alphabet = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
-
- switch ($hash_type) {
- case 'DES':
- $alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
- $length = 2;
- $salt = _php_crypt_random_string($alphabet, $length);
- return $salt;
-
- case 'MD5':
- $length = 12;
- $algorithm = '1';
- $salt = _php_crypt_random_string($alphabet, $length);
- return sprintf('$%s$%s', $algorithm, $salt);
-
- case 'BLOWFISH':
- $length = 22;
- if (empty($hash_difficulty)) {
- $cost = 10;
- } else {
- $cost = (int)$hash_difficulty;
- if ($cost < 4 || $cost > 31) {
- die('invalid encrypt difficulty setting "' . $hash_difficulty . '" for ' . $hash_type . ', the valid range is 4-31');
- }
- }
- if (version_compare(PHP_VERSION, '5.3.7') >= 0) {
- $algorithm = '2y'; // bcrypt, with fixed unicode problem
- } else {
- $algorithm = '2a'; // bcrypt
- }
- $salt = _php_crypt_random_string($alphabet, $length);
- return sprintf('$%s$%02d$%s', $algorithm, $cost, $salt);
-
- case 'SHA256':
- $length = 16;
- $algorithm = '5';
- if (empty($hash_difficulty)) {
- $rounds = '';
- } else {
- $rounds = (int)$hash_difficulty;
- if ($rounds < 1000 || $rounds > 999999999) {
- die('invalid encrypt difficulty setting "' . $hash_difficulty . '" for ' . $hash_type . ', the valid range is 1000-999999999');
- }
- }
- $salt = _php_crypt_random_string($alphabet, $length);
- if (!empty($rounds)) {
- $rounds = sprintf('rounds=%d$', $rounds);
- }
- return sprintf('$%s$%s%s', $algorithm, $rounds, $salt);
-
- case 'SHA512':
- $length = 16;
- $algorithm = '6';
- if (empty($hash_difficulty)) {
- $rounds = '';
- } else {
- $rounds = (int)$hash_difficulty;
- if ($rounds < 1000 || $rounds > 999999999) {
- die('invalid encrypt difficulty setting "' . $hash_difficulty . '" for ' . $hash_type . ', the valid range is 1000-999999999');
- }
- }
- $salt = _php_crypt_random_string($alphabet, $length);
- if (!empty($rounds)) {
- $rounds = sprintf('rounds=%d$', $rounds);
- }
- return sprintf('$%s$%s%s', $algorithm, $rounds, $salt);
-
- default:
- die("unknown hash type: '$hash_type'");
- }
-}
-
-/**
- * Generates a random string of specified $length from $characters.
- * @param string $characters
- * @param int $length
- * @return string of given $length
- */
-function _php_crypt_random_string($characters, $length) {
- $string = '';
- for ($p = 0; $p < $length; $p++) {
- $string .= $characters[random_int(0, strlen($characters) -1)];
- }
- return $string;
-}
-
-
-/**
- * Encrypt a password, using the apparopriate hashing mechanism as defined in
- * config.inc.php ($CONF['encrypt']).
- *
- * When wanting to compare one pw to another, it's necessary to provide the salt used - hence
- * the second parameter ($pw_db), which is the existing hash from the DB.
- *
- * @param string $pw
- * @param string $pw_db optional encrypted password
- * @return string encrypted password - if this matches $pw_db then the original password is $pw.
- */
-function pacrypt($pw, $pw_db="") {
- global $CONF;
-
- switch ($CONF['encrypt']) {
- case 'md5crypt':
- return _pacrypt_md5crypt($pw, $pw_db);
- case 'md5':
- return md5($pw);
- case 'system':
- return _pacrypt_crypt($pw, $pw_db);
- case 'cleartext':
- return $pw;
- case 'mysql_encrypt':
- return _pacrypt_mysql_encrypt($pw, $pw_db);
- case 'authlib':
- return _pacrypt_authlib($pw, $pw_db);
- }
-
- if (preg_match("/^dovecot:/", $CONF['encrypt'])) {
- return _pacrypt_dovecot($pw, $pw_db);
- }
-
- if (substr($CONF['encrypt'], 0, 9) === 'php_crypt') {
- return _pacrypt_php_crypt($pw, $pw_db);
- }
-
- die('unknown/invalid $CONF["encrypt"] setting: ' . $CONF['encrypt']);
-}
-
-/**
- * Creates MD5 based crypt formatted password.
- * If salt is not provided we generate one.
- *
- * @param string $pw plain text password
- * @param string $salt (optional)
- * @param string $magic (optional)
- * @return string hashed password in crypt format.
- */
-function md5crypt($pw, $salt="", $magic="") {
- $MAGIC = "$1$";
-
- if ($magic == "") {
- $magic = $MAGIC;
- }
- if ($salt == "") {
- $salt = create_salt();
- }
- $slist = explode("$", $salt);
- if ($slist[0] == "1") {
- $salt = $slist[1];
- }
-
- $salt = substr($salt, 0, 8);
- $ctx = $pw . $magic . $salt;
- $final = hex2bin(md5($pw . $salt . $pw));
-
- for ($i=strlen($pw); $i>0; $i-=16) {
- if ($i > 16) {
- $ctx .= substr($final, 0, 16);
- } else {
- $ctx .= substr($final, 0, $i);
- }
- }
- $i = strlen($pw);
-
- while ($i > 0) {
- if ($i & 1) {
- $ctx .= chr(0);
- } else {
- $ctx .= $pw[0];
- }
- $i = $i >> 1;
- }
- $final = hex2bin(md5($ctx));
-
- for ($i=0;$i<1000;$i++) {
- $ctx1 = "";
- if ($i & 1) {
- $ctx1 .= $pw;
- } else {
- $ctx1 .= substr($final, 0, 16);
- }
- if ($i % 3) {
- $ctx1 .= $salt;
- }
- if ($i % 7) {
- $ctx1 .= $pw;
- }
- if ($i & 1) {
- $ctx1 .= substr($final, 0, 16);
- } else {
- $ctx1 .= $pw;
- }
- $final = hex2bin(md5($ctx1));
- }
- $passwd = "";
- $passwd .= to64(((ord($final[0]) << 16) | (ord($final[6]) << 8) | (ord($final[12]))), 4);
- $passwd .= to64(((ord($final[1]) << 16) | (ord($final[7]) << 8) | (ord($final[13]))), 4);
- $passwd .= to64(((ord($final[2]) << 16) | (ord($final[8]) << 8) | (ord($final[14]))), 4);
- $passwd .= to64(((ord($final[3]) << 16) | (ord($final[9]) << 8) | (ord($final[15]))), 4);
- $passwd .= to64(((ord($final[4]) << 16) | (ord($final[10]) << 8) | (ord($final[5]))), 4);
- $passwd .= to64(ord($final[11]), 2);
- return "$magic$salt\$$passwd";
-}
-
-/**
- * @return string - should be random, 8 chars long
- */
-function create_salt() {
- srand((int) microtime()*1000000);
- $salt = substr(md5("" . rand(0, 9999999)), 0, 8);
- return $salt;
-}
-
-/*
- * remove item $item from array $array
- */
-function remove_from_array($array, $item) {
- # array_diff might be faster, but doesn't provide an easy way to know if the value was found or not
- # return array_diff($array, array($item));
- $ret = array_search($item, $array);
- if ($ret === false) {
- $found = 0;
- } else {
- $found = 1;
- unset($array[$ret]);
- }
- return array($found, $array);
-}
-
-function to64($v, $n) {
- $ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
- $ret = "";
- while (($n - 1) >= 0) {
- $n--;
- $ret .= $ITOA64[$v & 0x3f];
- $v = $v >> 6;
- }
- return $ret;
-}
-
-
-
-/**
- * smtp_mail
- * Action: Send email
- * Call: smtp_mail (string to, string from, string subject, string body]) - or -
- * Call: smtp_mail (string to, string from, string data) - DEPRECATED
- * @param String - To:
- * @param String - From:
- * @param String - Subject: (if called with 4 parameters) or full mail body (if called with 3 parameters)
- * @param String (optional) - Password
- * @param String (optional, but recommended) - mail body
- * @return bool - true on success, otherwise false
- * TODO: Replace this with something decent like PEAR::Mail or Zend_Mail.
- */
-function smtp_mail($to, $from, $data, $password = "", $body = "") {
- global $CONF;
- $smtpd_server = $CONF['smtp_server'];
- $smtpd_port = $CONF['smtp_port'];
- //$smtp_server = $_SERVER["SERVER_NAME"];
- $smtp_server = php_uname('n');
- if (!empty($CONF['smtp_client'])) {
- $smtp_server = $CONF['smtp_client'];
- }
- $errno = 0;
- $errstr = "0";
- $timeout = 30;
-
- if ($body != "") {
- $maildata =
- "To: " . $to . "\n"
- . "From: " . $from . "\n"
- . "Subject: " . encode_header($data) . "\n"
- . "MIME-Version: 1.0\n"
- . "Date: " . date('r') . "\n"
- . "Content-Type: text/plain; charset=utf-8\n"
- . "Content-Transfer-Encoding: 8bit\n"
- . "\n"
- . $body
- ;
- } else {
- $maildata = $data;
- }
-
- $fh = @fsockopen($smtpd_server, $smtpd_port, $errno, $errstr, $timeout);
-
- if (!$fh) {
- error_log("fsockopen failed - errno: $errno - errstr: $errstr");
- return false;
- } else {
- smtp_get_response($fh);
- fputs($fh, "EHLO $smtp_server\r\n");
- smtp_get_response($fh);
-
- fputs($fh, "STARTTLS\r\n");
- smtp_get_response($fh);
-
- if(false == stream_socket_enable_crypto($fh, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)){
- // fclose($smtp); // unsure if you need to close as I haven't run into a security fail at this point
- die("unable to start tls encryption");
- }
-
- if (!empty($password)) {
- fputs($fh,"AUTH LOGIN\r\n");
- smtp_get_response($fh);
- fputs($fh, base64_encode($from) . "\r\n");
- smtp_get_response($fh);
- fputs($fh, base64_encode($password) . "\r\n");
- smtp_get_response($fh);
- }
-
- fputs($fh, "MAIL FROM:<$from>\r\n");
- smtp_get_response($fh);
- fputs($fh, "RCPT TO:<$to>\r\n");
- smtp_get_response($fh);
- fputs($fh, "DATA\r\n");
- smtp_get_response($fh);
- fputs($fh, "$maildata\r\n.\r\n");
- smtp_get_response($fh);
- fputs($fh, "QUIT\r\n");
- smtp_get_response($fh);
- fclose($fh);
- }
- return true;
-}
-
-/**
- * smtp_get_admin_email
- * Action: Get configured email address or current user if nothing configured
- * Call: smtp_get_admin_email
- * @return string - username/mail address
- */
-function smtp_get_admin_email() {
- $admin_email = Config::read_string('admin_email');
- if (!empty($admin_email)) {
- return $admin_email;
- } else {
- return authentication_get_username();
- }
-}
-
-/**
- * smtp_get_admin_password
- * Action: Get smtp password for admin email
- * Call: smtp_get_admin_password
- * @return string - admin smtp password
- */
-function smtp_get_admin_password() {
- return Config::read_string('admin_smtp_password');
-}
-
-
-//
-// smtp_get_response
-// Action: Get response from mail server
-// Call: smtp_get_response (string FileHandle)
-//
-function smtp_get_response($fh) {
- $res ='';
- do {
- $line = fgets($fh, 256);
- $res .= $line;
- } while (preg_match("/^\d\d\d\-/", $line));
- echo $res;
- return $res;
-}
-
-
-
-$DEBUG_TEXT = <<FATAL Error:
Invalid \$CONF['database_type']! Please fix your config.inc.php!