diff -urN ../z-push-2.6.4+0.orig/src/backend/searchmysql/config.php.dist ./src/backend/searchmysql/config.php.dist --- ../z-push-2.6.4+0.orig/src/backend/searchmysql/config.php.dist 1970-01-01 03:00:00.000000000 +0300 +++ ./src/backend/searchmysql/config.php.dist 2023-06-19 14:48:36.113966000 +0300 @@ -0,0 +1,106 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +global $searchmysql_conf; +$searchmysql_conf = array(); +$i = 0; + + +// MySQL host, port, user, password and database +$searchmysql_conf[$i]["MYSQL_HOST"] = "127.0.0.1"; +$searchmysql_conf[$i]["MYSQL_PORT"] = 3306; +$searchmysql_conf[$i]["MYSQL_USER"] = "roundcube"; +$searchmysql_conf[$i]["MYSQL_PASSWORD"] = "roundcube"; +$searchmysql_conf[$i]["MYSQL_DATABASE"] = "roundcube"; + +// Search query +// the SEARCHVALUE string is substituded by the value inserted into the search field +// the MAILUSER string is substituded by $_SERVER['PHP_AUTH_USER'] or $_GET['User'] +$searchmysql_conf[$i]["MYSQL_SEARCH_QUERY"] = +"SELECT `contacts`.`name` AS displayname, `contacts`.`firstname` AS firstname, `contacts`.`surname` AS lastname, `contacts`.`email` AS email, `contacts`.`vcard` AS vcard +FROM `contacts`, `users` +WHERE + `users`.`username`='MAILUSER' AND `contacts`.`user_id`=`users`.`user_id` AND `contacts`.`del`=0 AND ( + `contacts`.`email` LIKE '%SEARCHVALUE%' + OR `contacts`.`name` LIKE '%SEARCHVALUE%' + OR `contacts`.`firstname` LIKE '%SEARCHVALUE%' + OR `contacts`.`surname` LIKE '%SEARCHVALUE%' + OR `contacts`.`words` LIKE '%SEARCHVALUE%' + ) +ORDER BY IF(LOCATE('SEARCHVALUE', `contacts`.`email`) = 1, 0, 1), IF(LOCATE('SEARCHVALUE', `contacts`.`name`) = 1, 0, 1), IF(LOCATE('SEARCHVALUE', `contacts`.`surname`) = 1, 0, 1);"; + +// SQL vcard field in search query above +$searchmysql_conf[$i]["MYSQL_SEARCH_QUERY_RESULT_VCARD_FIELD"] = "vcard"; + +$searchmysql_conf[$i]["FIX_DOMAIN_SEARCH"] = true; + + +$i++; + +// MySQL host, port, user, password and database +$searchmysql_conf[$i]["MYSQL_HOST"] = "127.0.0.1"; +$searchmysql_conf[$i]["MYSQL_PORT"] = 3306; +$searchmysql_conf[$i]["MYSQL_USER"] = "postfixadmin"; +$searchmysql_conf[$i]["MYSQL_PASSWORD"] = "postfixadmin"; +$searchmysql_conf[$i]["MYSQL_DATABASE"] = "postfixadmin"; + +// Search query +// the SEARCHVALUE string is substituded by the value inserted into the search field +// the MAILUSER string is substituded by $_SERVER['PHP_AUTH_USER'] or $_GET['User'] +$searchmysql_conf[$i]["MYSQL_SEARCH_QUERY"] = +"SELECT `name` AS displayname, `username` AS email +FROM `mailbox` +WHERE + `active`=1 AND ( + `username` LIKE '%SEARCHVALUE%' + OR `name` LIKE '%SEARCHVALUE%' + ) +ORDER BY IF(LOCATE('SEARCHVALUE', `username`) = 1, 0, 1), IF(LOCATE('SEARCHVALUE', `name`) = 1, 0, 1);"; + +// SQL vcard field in search query above +$searchmysql_conf[$i]["MYSQL_SEARCH_QUERY_RESULT_VCARD_FIELD"] = ""; + +$searchmysql_conf[$i]["FIX_DOMAIN_SEARCH"] = true; + + + +// SQL field mapping. +// values correspond to the search query above +global $mysql_field_map; +$mysql_field_map = array( + SYNC_GAL_DISPLAYNAME => 'displayname', + SYNC_GAL_PHONE => 'vcard_phone', + SYNC_GAL_OFFICE => 'vcard_x-department', + SYNC_GAL_TITLE => 'vcard_title', + SYNC_GAL_COMPANY => 'vcard_org', + SYNC_GAL_ALIAS => 'vcard_nickname', + SYNC_GAL_FIRSTNAME => 'firstname', + SYNC_GAL_LASTNAME => 'lastname', + SYNC_GAL_HOMEPHONE => 'vcard_tel_home', + SYNC_GAL_MOBILEPHONE => 'vcard_tel_cell', + SYNC_GAL_EMAILADDRESS => 'email', + ); diff -urN ../z-push-2.6.4+0.orig/src/backend/searchmysql/searchmysql.php ./src/backend/searchmysql/searchmysql.php --- ../z-push-2.6.4+0.orig/src/backend/searchmysql/searchmysql.php 1970-01-01 03:00:00.000000000 +0300 +++ ./src/backend/searchmysql/searchmysql.php 2023-09-26 20:09:11.517247000 +0300 @@ -0,0 +1,298 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +/********************************************************************* + * The BackendSearchMySQL is a stub to implement own search funtionality + * + * If you wish to implement an alternative search method, you should implement the + * ISearchProvider interface like the BackendSearchMySQL backend + */ + +require_once("backend/searchmysql/config.php"); + +class BackendSearchMySQL implements ISearchProvider{ + private $connection = false; + + /** + * Constructor + * initializes the searchprovider to perform the search + * + * @access public + * @return + * @throws StatusException, FatalException + */ + public function __construct() { + global $searchmysql_conf; + + if (!function_exists("mysqli_connect")) { + throw new StatusException("BackendSearchMySQL(): php-mysqli is not installed. Search aborted.", SYNC_SEARCHSTATUS_STORE_SERVERERROR, null, LOGLEVEL_FATAL); + } + + if (empty($searchmysql_conf) or !is_array($searchmysql_conf) or (count($searchmysql_conf) == 0)) { + throw new StatusException("BackendSearchMySQL(): settings not defined. Search aborted. " . print_r($searchmysql_conf), SYNC_SEARCHSTATUS_STORE_SERVERERROR, null, LOGLEVEL_FATAL); + } + + for ($i = 0; $i < count($searchmysql_conf) ; $i++) { + if (empty($searchmysql_conf[$i]["MYSQL_HOST"])) { + throw new StatusException("BackendSearchMySQL(): MYSQL_HOST not defined. Search aborted.", SYNC_SEARCHSTATUS_STORE_SERVERERROR, null, LOGLEVEL_FATAL); + } + if (empty($searchmysql_conf[$i]["MYSQL_USER"])) { + throw new StatusException("BackendSearchMySQL(): MYSQL_USER not defined. Search aborted.", SYNC_SEARCHSTATUS_STORE_SERVERERROR, null, LOGLEVEL_FATAL); + } + if (empty($searchmysql_conf[$i]["MYSQL_PASSWORD"])) { + throw new StatusException("BackendSearchMySQL(): MYSQL_PASSWORD not defined. Search aborted.", SYNC_SEARCHSTATUS_STORE_SERVERERROR, null, LOGLEVEL_FATAL); + } + if (empty($searchmysql_conf[$i]["MYSQL_DATABASE"])) { + throw new StatusException("BackendSearchMySQL(): MYSQL_DATABASE not defined. Search aborted.", SYNC_SEARCHSTATUS_STORE_SERVERERROR, null, LOGLEVEL_FATAL); + } + if (empty($searchmysql_conf[$i]["MYSQL_PORT"])) { + $searchmysql_conf[$i]["MYSQL_PORT"] = 3306; + } elseif (!is_int($searchmysql_conf[$i]["MYSQL_PORT"])) { + throw new StatusException("BackendSearchMySQL(): MYSQL_PORT " . $searchmysql_conf[$i]["MYSQL_PORT"] . " for MySQL server " . $searchmysql_conf[$i]["MYSQL_HOST"] . " in not a numeric. Search aborted.", SYNC_SEARCHSTATUS_STORE_SERVERERROR, null, LOGLEVEL_FATAL); + } + + // connect to MySQL + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendSearchMySQL->__construct(): Try to connect to MySQL server %s:%d as user '%s'", $searchmysql_conf[$i]["MYSQL_HOST"], $searchmysql_conf[$i]["MYSQL_PORT"], $searchmysql_conf[$i]["MYSQL_USER"])); + $this->connection[$i] = @mysqli_connect($searchmysql_conf[$i]["MYSQL_HOST"], $searchmysql_conf[$i]["MYSQL_USER"], $searchmysql_conf[$i]["MYSQL_PASSWORD"], $searchmysql_conf[$i]["MYSQL_DATABASE"], $searchmysql_conf[$i]["MYSQL_PORT"]); + if (!$this->connection[$i]) { + if (mysqli_connect_errno()) { + $error_message = sprintf( + "BackendSearchMySQL(): Could not connect to MySQL server %s:%d (database %s): error %d (%s). Search aborted.", + $searchmysql_conf[$i]["MYSQL_HOST"], $searchmysql_conf[$i]["MYSQL_PORT"], $searchmysql_conf[$i]["MYSQL_DATABASE"], + mysqli_connect_errno($this->connection[$i]), mysqli_connect_error($this->connection[$i]) + ); + $this->connection[$i] = false; + throw new StatusException($error_message, SYNC_SEARCHSTATUS_STORE_CONNECTIONFAILED, null, LOGLEVEL_ERROR); + } else { + $error_message = sprintf( + "BackendSearchMySQL(): Could not authorize as user '%s' and specified password for accessing to db '%s' on MySQL server %s:%d: error %d (%s). Search aborted.", + $searchmysql_conf[$i]["MYSQL_USER"], $searchmysql_conf[$i]["MYSQL_DATABASE"], $searchmysql_conf[$i]["MYSQL_HOST"], $searchmysql_conf[$i]["MYSQL_PORT"], + mysqli_errno($this->connection[$i]), mysqli_error($this->connection[$i]) + ); + @mysqli_close($this->connection[$i]); + $this->connection[$i] = false; + throw new StatusException($error_message, SYNC_SEARCHSTATUS_STORE_ACCESSDENIED, null, LOGLEVEL_ERROR); + } + } + + $mysql_query = "SET NAMES 'utf8';"; + if (!@mysqli_query($this->connection[$i], $mysql_query)) { + $error_message = sprintf( + "BackendSearchMySQL(): Error executing query '%s' to database %s on MySQL server %s:%d: error %d (%s).", + $mysql_query, $searchmysql_conf[$i]["MYSQL_DATABASE"], $searchmysql_conf[$i]["MYSQL_HOST"], $searchmysql_conf[$i]["MYSQL_PORT"], + mysqli_errno($this->connection[$i]), mysqli_error($this->connection[$i]) + ); + ZLog::Write(LOGLEVEL_ERROR, $error_message); + return false; + } + } + } + + /** + * Indicates if a search type is supported by this SearchProvider + * Currently only the type ISearchProvider::SEARCH_GAL (Global Address List) is implemented + * + * @param string $searchtype + * + * @access public + * @return boolean + */ + public function SupportsType($searchtype) { + return ($searchtype == ISearchProvider::SEARCH_GAL); + } + + /** + * Searches the GAL + * + * @param string $searchquery string to be searched for + * @param string $searchrange specified searchrange + * @param SyncResolveRecipientsPicture $searchpicture limitations for picture + * + * @access public + * @return array search results + * @throws StatusException + */ + public function GetGALSearchResults($searchquery, $searchrange, $searchpicture) { + global $mysql_field_map; + global $searchmysql_conf; + + $username = Request::GetAuthUser(); + if (empty($username)) $username = Request::GetGETUser(); + if (empty($username)) { +// throw new StatusException("BackendSearchMySQL->GetGALSearchResults(): Unable to determine mail user. Search aborted.", SYNC_SEARCHSTATUS_STORE_SERVERERROR, null, LOGLEVEL_FATAL); + ZLog::Write(LOGLEVEL_ERROR, "BackendSearchMySQL->GetGALSearchResults(): Unable to determine mail user. Search aborted"); + return false; + } + + $items = array(); + $unique = array(); + + for ($i = 0; $i < count($searchmysql_conf) ; $i++) { + if (isset($this->connection[$i]) and $this->connection[$i] !== false) { + $mysql_search_query = str_replace("MAILUSER", mysqli_real_escape_string($this->connection[$i], $username), str_replace("SEARCHVALUE", $searchquery, $searchmysql_conf[$i]["MYSQL_SEARCH_QUERY"])); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendSearchMySQL->GetGALSearchResults(): execute query '%s' to database %s on MySQL server %s:%d", str_replace("\n", " ", $mysql_search_query), $searchmysql_conf[$i]["MYSQL_DATABASE"], $searchmysql_conf[$i]["MYSQL_HOST"], $searchmysql_conf[$i]["MYSQL_PORT"])); + $result = @mysqli_query($this->connection[$i], $mysql_search_query); + if (!$result) { + $error_message = sprintf( + "BackendSearchMySQL->GetGALSearchResults(): Error executing query '%s' to database %s on MySQL server %s:%d: error %d (%s). Search aborted.", + str_replace("\n", " ", $mysql_search_query), $searchmysql_conf[$i]["MYSQL_DATABASE"], $searchmysql_conf[$i]["MYSQL_HOST"], $searchmysql_conf[$i]["MYSQL_PORT"], + mysqli_errno($this->connection[$i]), mysqli_error($this->connection[$i]) + ); + ZLog::Write(LOGLEVEL_ERROR, $error_message); + return false; + } + + $rc = count($items); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendSearchMySQL->GetGALSearchResults(): got %d results", mysqli_num_rows($result))); + while ($item = mysqli_fetch_array($result, MYSQLI_ASSOC)) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendSearchMySQL->GetGALSearchResults(): got row: %s", print_r($item, true))); + if ((!empty($searchmysql_conf[$i]["MYSQL_SEARCH_QUERY_RESULT_VCARD_FIELD"])) and (!empty($item[$searchmysql_conf[$i]["MYSQL_SEARCH_QUERY_RESULT_VCARD_FIELD"]]))) { + $vcard = preg_split('/\r?\n/', $item[$searchmysql_conf[$i]["MYSQL_SEARCH_QUERY_RESULT_VCARD_FIELD"]]); + foreach ($vcard as $vcard_line) { + if (!empty($vcard_line)) { + $pos = strpos($vcard_line, ':'); + if ($pos !== false) { + $vcard_key = str_replace(';type=', '_', strtolower(substr($vcard_line, 0, $pos))); + $vcard_value = substr($vcard_line, ($pos + 1)); + $item['vcard_' . $vcard_key] = $vcard_value; + } + } + } + } + + if ((!empty($item['email'])) and (empty($unique[$item['email']]))) { + ZLog::Write(LOGLEVEL_DEBUG, "BackendSearchMySQL->GetGALSearchResults(): email is unique"); + $unique[$item['email']] = true; + + if (empty($item['displayname'])) { + if (!empty($item['firstname'])) $item['displayname'] = $item['firstname']; + if (!empty($item['lastname'])) { + $item['displayname'] .= (empty($item['displayname']) ? '' : ' ') . $item['lastname']; + } + } + if (empty($item['displayname'])) { + $item['displayname'] = preg_replace('/\@.+/', '', $item['email']); + } + if ((!empty($item['displayname'])) and ($item['displayname'] == $item['email'])) { + $item['displayname'] = preg_replace('/\@.+/', '', $item['email']); + } + + foreach ($mysql_field_map as $key => $value ) { + if (isset($item[$value])) { + $items[$rc][$key] = $item[$value]; + } + } + + if (isset($searchmysql_conf[$i]["FIX_DOMAIN_SEARCH"]) and $searchmysql_conf[$i]["FIX_DOMAIN_SEARCH"]) { + if (!empty($items[$rc][SYNC_GAL_DISPLAYNAME])) $items[$rc][SYNC_GAL_DISPLAYNAME] = preg_replace('/@(\S+)/', ' ($1)', $items[$rc][SYNC_GAL_DISPLAYNAME]); + if (!empty($items[$rc][SYNC_GAL_FIRSTNAME])) $items[$rc][SYNC_GAL_FIRSTNAME] = preg_replace('/@(\S+)/', ' ($1)', $items[$rc][SYNC_GAL_FIRSTNAME]); + if (!empty($items[$rc][SYNC_GAL_LASTNAME])) $items[$rc][SYNC_GAL_LASTNAME] = preg_replace('/@(\S+)/', ' ($1)', $items[$rc][SYNC_GAL_LASTNAME]); + list($local_part, $domain) = preg_split('/@/', $items[$rc][SYNC_GAL_EMAILADDRESS]); + if ( + (mb_stripos($domain, $searchquery) !== false) + and (mb_stripos($local_part, $searchquery) === false) + and (mb_stripos($items[$rc][SYNC_GAL_DISPLAYNAME], $searchquery) === false) + ) { + $items[$rc][SYNC_GAL_DISPLAYNAME] .= ' (' . $domain . ')'; + $items[$rc][SYNC_GAL_LASTNAME] .= ' (' . $domain . ')'; + } + } + + $rc++; + } + } + + mysqli_free_result($result); + } + } + + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendSearchMySQL->GetGALSearchResults(): got total items: %s", print_r($items, true))); + + $searchtotal = count($items); + + $rangestart = 0; + $rangeend = 50; + if (!empty($searchrange) and ($searchrange != '0')) { + $pos = strpos($searchrange, '-'); + if ($pos !== false) { + $rangestart = substr($searchrange, 0, $pos); + $rangeend = substr($searchrange, ($pos + 1)); + } + } + + while (count($items) > $rangeend + 1) array_pop($items); + for ($i = 0; $i < $rangestart; $i++) { + if (count($items) > 0) array_shift($items); + } + $items['range'] = $rangestart . '-' . ($rangestart + count($items) - 1); + $items['searchtotal'] = $searchtotal; + +// ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendSearchMySQL->GetGALSearchResults(): got ranged items: %s", print_r($items, true))); + + return $items; + } + + /** + * Searches for the emails on the server + * + * @param ContentParameter $cpo + * + * @return array + */ + public function GetMailboxSearchResults($cpo){ + return array(); + } + + /** + * Terminates a search for a given PID + * + * @param int $pid + * + * @return boolean + */ + public function TerminateSearch($pid) { + return true; + } + + /** + * Disconnects from the current search provider + * + * @access public + * @return boolean + */ + public function Disconnect() { + global $searchmysql_conf; + for ($i = 0; $i < count($searchmysql_conf) ; $i++) { + if ($this->connection[$i]) @mysqli_close($this->connection[$i]); + $this->connection[$i] = false; + } + + return true; + } +}