Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
20 KB
Referenced Files
None
Subscribers
None
diff --git a/bin/inspect.php b/bin/inspect.php
index 2b2a832..bf2ba21 100755
--- a/bin/inspect.php
+++ b/bin/inspect.php
@@ -1,332 +1,333 @@
#!/usr/bin/env php
<?php
/*
+--------------------------------------------------------------------------+
| Kolab Sync (ActiveSync for Kolab) |
| |
| Copyright (C) 2024, Apheleia IT AG <contact@apheleia-it.ch> |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Christian Mollekopf <mollekopf@apheleia-it.ch> |
+--------------------------------------------------------------------------+
*/
require_once 'shared.php';
function filterTypeToIMAPSearch($filter_type = 0)
{
switch ($filter_type) {
case 1:
$mod = '-1 day';
break;
case 2:
$mod = '-3 days';
break;
case 3:
$mod = '-1 week';
break;
case 4:
$mod = '-2 weeks';
break;
case 5:
$mod = '-1 month';
break;
}
if (!empty($mod)) {
$dt = new DateTime('now', new DateTimeZone('UTC'));
$dt->modify($mod);
// RFC3501: IMAP SEARCH
return 'SINCE ' . $dt->format('d-M-Y');
}
return "";
}
$opts = rcube_utils::get_opt([
'e' => 'email',
'p' => 'adminpassword',
'd' => 'debug',
'k' => 'dump',
]);
if (empty($opts['email'])) {
rcube::raise_error("Email address not specified (--email).", false, true);
}
$email = $opts['email'];
$cli = new SyncrotonCli();
$userid = $cli->selectUser($opts['email'] ?? null);
$db = $cli->db;
-$imap = $cli->connectToImap($opts['email'] ?? null, $opts['password'] ?? null, $opts['adminpassword'] ?? null, !empty($opts['debug']));
+$debug = !empty($opts['email']);
+$imap = $cli->connectToImap($opts['email'] ?? null, $opts['password'] ?? null, $opts['adminpassword'] ?? null, $debug);
$devicesSelect = $db->query(
"SELECT `id`, `deviceid`, `devicetype` FROM `syncroton_device`"
. " WHERE `owner_id` = ?",
$userid
);
$result = [];
while ($data = $db->fetch_assoc($devicesSelect)) {
$deviceid = $data["deviceid"];
$device_id = $data["id"];
$result[$device_id]['deviceid'] = $deviceid;
$result[$device_id]['device_id'] = $device_id;
$result[$device_id]['devicetype'] = $data["devicetype"];
$select = $db->limitquery(
"SELECT `counter`, `lastsync` FROM `syncroton_synckey`"
. " WHERE `device_id` = ? AND `type` = 'FolderSync'"
. " ORDER BY `counter` DESC",
0,
1,
$device_id
);
if ($data = $db->fetch_assoc($select)) {
$result[$device_id]['FolderSync'] = [
"counter" => $data['counter'],
"lastsync" => $data['lastsync'],
];
} else {
echo("Synckey not found.\n");
}
$folderSelect = $db->query(
"SELECT * FROM `syncroton_folder`"
. " WHERE `device_id` = ?",
$device_id
);
while ($folder = $db->fetch_assoc($folderSelect)) {
$select = $db->limitquery(
"SELECT `counter`, `lastsync`, `extra_data` FROM `syncroton_synckey`"
. " WHERE `device_id` = ? AND `type` = ?"
. " ORDER BY `counter` DESC",
0,
1,
$device_id,
$folder['id']
);
if ($data = $db->fetch_assoc($select)) {
$result[$device_id]['folders'][$folder['id']] = [
"counter" => $data['counter'],
"lastsync" => $data['lastsync'],
"modseq" => $data['extra_data'] ? json_decode($data['extra_data'])->modseq : null,
];
}
$result[$device_id]['folders'][$folder['id']]['name'] = $folder['displayname'];
$result[$device_id]['folders'][$folder['id']]['folderid'] = $folder['folderid'];
$result[$device_id]['folders'][$folder['id']]['class'] = $folder['class'];
$result[$device_id]['folders'][$folder['id']]['lastfiltertype'] = $folder['lastfiltertype'] ?? null;
$imap->select($folder['displayname']);
$result[$device_id]['folders'][$folder['id']]['imapModseq'] = $imap->data['HIGHESTMODSEQ'] ?? null;
$index = $imap->search(
$folder['displayname'],
'ALL UNDELETED ' . filterTypeToIMAPSearch($folder['lastfiltertype']),
false,
['COUNT']
);
if (!$index->is_error()) {
$result[$device_id]['folders'][$folder['id']]['imapMessagecount'] = $index->count();
}
$select = $db->query(
"SELECT count(*) FROM `syncroton_content`"
. " WHERE `device_id` = ? AND `folder_id` = ?",
$device_id,
$folder['id']
);
if ($data = $db->fetch_assoc($select)) {
$result[$device_id]['folders'][$folder['id']]['contentCount'] = array_values($data)[0];
}
}
}
if ($debug) {
var_export($result);
}
function println($output)
{
print("{$output}\n");
}
function filterType($value)
{
if (!$value) {
return "No filter";
}
switch ($value) {
case 0: return "No filter";
case 1: return "1 day";
case 2: return "3 days";
case 3: return "1 week";
case 4: return "2 weeks";
case 5: return "1 month";
case 6: return "3 months (WARNING: not implemented)";
case 7: return "6 months (WARNING: not implemented)";
case 8: return "Filter by incomplete tasks";
}
return "Unknown value: $value";
}
function getContentIds($db, $device_id, $folder_id)
{
$contentSelect = $db->query(
"SELECT contentid FROM `syncroton_content`"
. " WHERE `device_id` = ? AND `folder_id` = ? AND `is_deleted` = 0",
$device_id,
$folder_id
);
$contentUids = [];
while ($content = $db->fetch_assoc($contentSelect)) {
$contentUids[] = $content['contentid'];
}
return $contentUids;
}
function getContentUids($db, $device_id, $folder_id)
{
$contentSelect = $db->query(
"SELECT contentid FROM `syncroton_content`"
. " WHERE `device_id` = ? AND `folder_id` = ? AND `is_deleted` = 0",
$device_id,
$folder_id
);
$contentUids = [];
while ($content = $db->fetch_assoc($contentSelect)) {
$contentUids[] = explode('::', $content['contentid'])[1];
}
return $contentUids;
}
function getImapUids($imap, $folder, $lastfiltertype)
{
$imap->select($folder);
$index = $imap->search($folder, 'ALL UNDELETED ' . filterTypeToIMAPSearch($lastfiltertype), true);
if (!$index->is_error()) {
return $index->get();
}
return [];
}
println("");
foreach ($result as $deviceId => $values) {
$device_id = $values['deviceid'];
println("Device: $device_id ($deviceId)");
println(" Last folder sync: " . $values['FolderSync']['lastsync']);
println(" Folder sync count: " . $values['FolderSync']['counter']);
println(" Folders:");
foreach ($values['folders'] ?? [] as $folderId => $folder) {
$folder_id = $folder['folderid'];
println(" " . $folder['name'] . " $folder_id ($folderId)");
$messageCount = $folder['contentCount'];
$totalCount = $folder['imapMessagecount'] ?? "unknown";
$modseq = $folder['modseq'] ?? "none";
$imapModseq = $folder['imapModseq'];
// We're not using modseq for groupware folders
if ($messageCount == $totalCount && ($modseq == "none" || $modseq == $imapModseq)) {
println(" Status: Fully Synced ($messageCount messages)");
} else {
println(" Status: Incomplete ($messageCount/$totalCount messages)");
println(" Modseq: " . $modseq . "/" . $imapModseq);
}
println(" Last sync: " . ($folder['lastsync'] ?? "None"));
println(" Number of syncs: " . ($folder['counter'] ?? "None"));
println(" Filter type: " . filterType($folder['lastfiltertype'] ?? null));
if (!empty($opts['dump']) && $opts['dump'] == $folder['name']) {
$contentUids = getContentUids($db, $deviceId, $folderId);
$imapUids = getImapUids($imap, $folder['name'], $folder['lastfiltertype'] ?? null);
$entries = array_diff($imapUids, $contentUids);
if (!empty($entries)) {
println(" The following messages are on the server, but not the device:");
foreach ($entries as $uid) {
println(" $uid");
//TODO get details from imap?
}
}
$entries = array_diff($contentUids, $imapUids);
if (!empty($entries)) {
println(" The following messages are on the device, but not the server:");
foreach ($entries as $uid) {
println(" $uid");
//TODO get details from the content part?
//TODO display creation_synckey?
}
}
$contentUids = getContentUids($db, $deviceId, $folderId);
foreach (array_slice($contentUids, -10, 10) as $uid) {
println($uid);
}
$contentIds = getContentIds($db, $deviceId, $folderId);
foreach (array_slice($contentIds, -10, 10) as $uid) {
println($uid);
}
}
if (($folder['class'] == "Email") && ($folder['counter'] ?? false) && $messageCount != $totalCount && ($modseq == "none" || $modseq == $imapModseq)) {
if (($folder['lastfiltertype'] ?? false) && $messageCount > $totalCount) {
// This doesn't have to indicate an issue, since the timewindow of the filter wanders, so some messages that have been synchronized may no longer match the window.
} else {
println(" Issue Detected: The sync state seems to be inconsistent. The device should be fully synced, but the sync counts differ.");
println(" There are $messageCount ContentParts (should match number of messages on the device), but $totalCount messages in IMAP matching the filter.");
$contentUids = getContentUids($db, $deviceId, $folderId);
$imapUids = getImapUids($imap, $folder['name'], $folder['lastfiltertype'] ?? null);
$entries = array_diff($imapUids, $contentUids);
if (!empty($entries)) {
println(" The following messages are on the server, but not the device:");
foreach ($entries as $uid) {
println(" $uid");
//TODO get details from imap?
}
}
$entries = array_diff($contentUids, $imapUids);
if (!empty($entries)) {
println(" The following messages are on the device, but not the server:");
foreach ($entries as $uid) {
println(" $uid");
//TODO get details from the content part?
//TODO display creation_synckey?
}
}
println("");
}
}
println("");
}
}
diff --git a/bin/shared.php b/bin/shared.php
index 5657408..2e917fa 100644
--- a/bin/shared.php
+++ b/bin/shared.php
@@ -1,206 +1,210 @@
<?php
/*
+--------------------------------------------------------------------------+
| Kolab Sync (ActiveSync for Kolab) |
| |
| Copyright (C) 2024, Apheleia IT AG <contact@apheleia-it.ch> |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Christian Mollekopf <mollekopf@apheleia-it.ch> |
+--------------------------------------------------------------------------+
*/
define('RCUBE_INSTALL_PATH', realpath(dirname(__FILE__) . '/../') . '/');
define('RCUBE_PLUGINS_DIR', RCUBE_INSTALL_PATH . 'lib/plugins/');
// Define include path
$include_path = RCUBE_INSTALL_PATH . 'lib' . PATH_SEPARATOR;
$include_path .= RCUBE_INSTALL_PATH . 'lib/ext' . PATH_SEPARATOR;
$include_path .= ini_get('include_path');
set_include_path($include_path);
// include composer autoloader (if available)
if (@file_exists(RCUBE_INSTALL_PATH . 'vendor/autoload.php')) {
require RCUBE_INSTALL_PATH . 'vendor/autoload.php';
}
// include global functions from Roundcube Framework
require_once 'Roundcube/bootstrap.php';
class SyncrotonCli {
public $rcube = null;
public $userid = null;
public $host;
public $db;
public $imap = null;
public $sync = null;
function __construct() {
$this->rcube = \rcube::get_instance();
$host = $this->rcube->config->get('imap_host', $this->rcube->config->get('default_host'));
$this->host = parse_url($host, PHP_URL_HOST) ?? $host;
$this->port = parse_url($host, PHP_URL_PORT);
$this->db = $this->rcube->get_dbh();
ini_set('display_errors', 1);
error_reporting(E_ALL);
$this->sync = kolab_sync::get_instance();
}
public function connectToImap($email, $password, $adminpassword, $debug) {
$proxyAuth = false;
if ($adminpassword) {
$proxyAuth = true;
//FIXME will not work for dovecot
$user = "cyrus-admin";
$password = $adminpassword;
}
if (empty($password)) {
rcube::raise_error("Password not specified (--adminpassword/--password).", false, true);
}
$imap = new \rcube_imap_generic();
if ($proxyAuth) {
$options['auth_cid'] = $user;
$options['auth_pw'] = $password;
}
$options['auth_type'] = 'PLAIN';
$options['port'] = $this->port;
+ if ($this->port == 993) {
+ $options['ssl_mode'] = 'ssl';
+ }
+
$options['socket_options'] = [
'ssl' => [
'verify_peer_name' => false,
'verify_peer' => false,
'allow_self_signed' => true,
],
];
$imap->setDebug($debug);
if (!$imap->connect($this->host, $email, $password, $options)) {
rcube::raise_error("Failed to connect to imap.", false, true);
}
$this->imap = $imap;
return $imap;
}
public function authenticate($password)
{
$this->rcube->set_user(new rcube_user($this->userid));
if ($this->sync->authenticate($this->email, $password)) {
$this->sync->password = $password;
}
}
public function getStorage($password)
{
$this->authenticate($password);
return $this->sync->storage();
}
public function getImapStorage($password)
{
$this->authenticate($password);
return $this->sync->get_storage();
}
public function selectUser($user) {
$select = $this->db->query(
"SELECT `user_id` FROM `users`"
. " WHERE `username` = ?"
. " AND `mail_host` = ?"
. " ORDER BY `user_id` DESC",
\strtolower($user),
\strtolower($this->host)
);
if ($data = $this->db->fetch_assoc($select)) {
$this->userid = $data['user_id'];
} else {
\rcube::raise_error("User not found in Roundcube database: {$user}", false, true);
}
print("Found the user with id: {$this->userid}\n");
$this->rcube->set_user(new rcube_user($this->userid));
$this->email = $user;
return $this->userid;
}
public function selectDevices($deviceid, $devicetype) {
$devices = [];
if (!empty($deviceid)) {
$select = $this->db->query(
"SELECT `id` FROM `syncroton_device` WHERE `owner_id` = ? AND ( `deviceid` = ? OR `id` = ? )",
$this->userid,
$deviceid,
$deviceid
);
} elseif (!empty($devicetype)) {
$select = $this->db->query(
"SELECT `id` FROM `syncroton_device` WHERE `owner_id` = ? AND `devicetype` = ?",
$this->userid,
$devicetype
);
} else {
$select = $this->db->query("SELECT `id` FROM `syncroton_device` WHERE `owner_id` = ?", $this->userid);
}
while ($record = $this->db->fetch_assoc($select)) {
$devices[] = $record['id'];
}
return $devices;
}
// Translate from device.id to device.deviceid
public function getDeviceId($id)
{
$select = $this->db->query(
"SELECT `deviceid` FROM `syncroton_device` WHERE `id` = ?",
$id
);
while ($record = $this->db->fetch_assoc($select)) {
return $record['deviceid'];
}
return null;
}
public function setSubscriptions($id, $type, $subscriptions) {
// This is essentially the same as kolab_subscriptions::set_subscriptions, but without the id translation.
$data = json_encode($subscriptions);
$db = $this->db;
$query = $db->query("SELECT 1 FROM syncroton_subscriptions WHERE `device_id` = ? AND `type` = ?", $id, $type);
if ($db->fetch_array($query)) {
$query = $db->query("UPDATE syncroton_subscriptions SET `data` = ? WHERE `device_id` = ? AND `type` = ?", $data, $id, $type);
} else {
$query = $db->query("INSERT INTO syncroton_subscriptions (`device_id`, `type`, `data`) VALUES (?, ?, ?)", $id, $type, $data);
}
return $db->affected_rows($query) > 0;
}
public function getSubscriptions($id, $type) {
$query = $this->db->query("SELECT `data` FROM syncroton_subscriptions WHERE `device_id` = ? AND `type` = ?", $id, $type);
if ($record = $this->db->fetch_assoc($query)) {
return json_decode($record['data'], true);
}
return [];
}
};

File Metadata

Mime Type
text/x-diff
Expires
Mon, Apr 6, 1:42 AM (2 d, 22 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18831868
Default Alt Text
(20 KB)

Event Timeline