Commit bcb2755a authored by Sebastian Kummer's avatar Sebastian Kummer

Merge branch 'release/2.2' of https://stash.z-hub.io/scm/zp/z-push

parents ffe08ad2 d3fa6014
...@@ -176,12 +176,38 @@ function listfolders_getlist ($adminStore, $session, $user) { ...@@ -176,12 +176,38 @@ function listfolders_getlist ($adminStore, $session, $user) {
exit (1); exit (1);
} }
$folder = @mapi_msgstore_openentry($userStore); if (strtoupper($user) != 'SYSTEM') {
$h_table = @mapi_folder_gethierarchytable($folder, CONVENIENT_DEPTH); $inbox = mapi_msgstore_getreceivefolder($userStore);
$subfolders = @mapi_table_queryallrows($h_table, array(PR_ENTRYID, PR_DISPLAY_NAME, PR_CONTAINER_CLASS, PR_SOURCE_KEY)); if(mapi_last_hresult() != NOERROR) {
printf("Could not open inbox for %s (0x%08X). The script will exit.\n", $user, mapi_last_hresult());
exit (1);
}
$inboxProps = mapi_getprops($inbox, array(PR_SOURCE_KEY));
}
$storeProps = mapi_getprops($userStore, array(PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID));
$root = @mapi_msgstore_openentry($userStore, null);
$h_table = @mapi_folder_gethierarchytable($root, CONVENIENT_DEPTH);
$subfolders = @mapi_table_queryallrows($h_table, array(PR_ENTRYID, PR_DISPLAY_NAME, PR_CONTAINER_CLASS, PR_SOURCE_KEY, PR_PARENT_SOURCE_KEY, PR_FOLDER_TYPE, PR_ATTR_HIDDEN));
echo "Available folders in store '$user':\n" . str_repeat("-", 50) . "\n"; echo "Available folders in store '$user':\n" . str_repeat("-", 50) . "\n";
foreach($subfolders as $folder) { foreach($subfolders as $folder) {
// do not display hidden and search folders
if ((isset($folder[PR_ATTR_HIDDEN]) && $folder[PR_ATTR_HIDDEN]) ||
(isset($folder[PR_FOLDER_TYPE]) && $folder[PR_FOLDER_TYPE] == FOLDER_SEARCH)) {
continue;
}
// handle some special folders
if ((strtoupper($user) != 'SYSTEM') &&
((isset($inboxProps[PR_SOURCE_KEY]) && $folder[PR_SOURCE_KEY] == $inboxProps[PR_SOURCE_KEY]) ||
$folder[PR_ENTRYID] == $storeProps[PR_IPM_SENTMAIL_ENTRYID] ||
$folder[PR_ENTRYID] == $storeProps[PR_IPM_WASTEBASKET_ENTRYID])) {
$folder[PR_CONTAINER_CLASS] = "IPF.Note";
}
if (isset($folder[PR_CONTAINER_CLASS]) && array_key_exists($folder[PR_CONTAINER_CLASS], $supported_classes)) { if (isset($folder[PR_CONTAINER_CLASS]) && array_key_exists($folder[PR_CONTAINER_CLASS], $supported_classes)) {
echo "Folder name:\t". $folder[PR_DISPLAY_NAME] . "\n"; echo "Folder name:\t". $folder[PR_DISPLAY_NAME] . "\n";
echo "Folder ID:\t". bin2hex($folder[PR_SOURCE_KEY]) . "\n"; echo "Folder ID:\t". bin2hex($folder[PR_SOURCE_KEY]) . "\n";
......
...@@ -205,6 +205,7 @@ class MAPIMapping { ...@@ -205,6 +205,7 @@ class MAPIMapping {
"representingentryid" => PR_SENT_REPRESENTING_ENTRYID, "representingentryid" => PR_SENT_REPRESENTING_ENTRYID,
"sourcekey" => PR_SOURCE_KEY, "sourcekey" => PR_SOURCE_KEY,
"entryid" => PR_ENTRYID, "entryid" => PR_ENTRYID,
"parentsourcekey" => PR_PARENT_SOURCE_KEY,
"body" => PR_BODY, "body" => PR_BODY,
"rtfcompressed" => PR_RTF_COMPRESSED, "rtfcompressed" => PR_RTF_COMPRESSED,
"html" => PR_HTML, "html" => PR_HTML,
......
...@@ -513,6 +513,15 @@ class MAPIProvider { ...@@ -513,6 +513,15 @@ class MAPIProvider {
* @return SyncEmail * @return SyncEmail
*/ */
private function getEmail($mapimessage, $contentparameters) { private function getEmail($mapimessage, $contentparameters) {
// This workaround fixes ZP-729 and still works with Outlook.
// FIXME: It should be properly fixed when refactoring.
$bpReturnType = Utils::GetBodyPreferenceBestMatch($contentparameters->GetBodyPreference());
if (($contentparameters->GetMimeSupport() == SYNC_MIMESUPPORT_NEVER) ||
($key = array_search(SYNC_BODYPREFERENCE_MIME, $contentparameters->GetBodyPreference()) === false) ||
$bpReturnType != SYNC_BODYPREFERENCE_MIME) {
MAPIUtils::ParseSmime($this->session, $this->store, $this->getAddressbook(), $mapimessage);
}
$message = new SyncMail(); $message = new SyncMail();
$this->getPropsFromMAPI($message, $mapimessage, MAPIMapping::GetEmailMapping()); $this->getPropsFromMAPI($message, $mapimessage, MAPIMapping::GetEmailMapping());
...@@ -668,6 +677,7 @@ class MAPIProvider { ...@@ -668,6 +677,7 @@ class MAPIProvider {
$attachtable = mapi_message_getattachmenttable($mapimessage); $attachtable = mapi_message_getattachmenttable($mapimessage);
$rows = mapi_table_queryallrows($attachtable, array(PR_ATTACH_NUM)); $rows = mapi_table_queryallrows($attachtable, array(PR_ATTACH_NUM));
$entryid = bin2hex($messageprops[$emailproperties["entryid"]]); $entryid = bin2hex($messageprops[$emailproperties["entryid"]]);
$parentSourcekey = bin2hex($messageprops[$emailproperties["parentsourcekey"]]);
foreach($rows as $row) { foreach($rows as $row) {
if(isset($row[PR_ATTACH_NUM])) { if(isset($row[PR_ATTACH_NUM])) {
...@@ -699,7 +709,7 @@ class MAPIProvider { ...@@ -699,7 +709,7 @@ class MAPIProvider {
// set AS version specific parameters // set AS version specific parameters
if (Request::GetProtocolVersion() >= 12.0) { if (Request::GetProtocolVersion() >= 12.0) {
$attach->filereference = $entryid.":".$row[PR_ATTACH_NUM]; $attach->filereference = sprintf("%s:%s:%s", $entryid, $row[PR_ATTACH_NUM], $parentSourcekey);
$attach->method = (isset($attachprops[PR_ATTACH_METHOD])) ? $attachprops[PR_ATTACH_METHOD] : ATTACH_BY_VALUE; $attach->method = (isset($attachprops[PR_ATTACH_METHOD])) ? $attachprops[PR_ATTACH_METHOD] : ATTACH_BY_VALUE;
// if displayname does not have the eml extension for embedde messages, android and WP devices won't open it // if displayname does not have the eml extension for embedde messages, android and WP devices won't open it
...@@ -707,7 +717,15 @@ class MAPIProvider { ...@@ -707,7 +717,15 @@ class MAPIProvider {
if (strtolower(substr($attach->displayname, -4)) != '.eml') if (strtolower(substr($attach->displayname, -4)) != '.eml')
$attach->displayname .= '.eml'; $attach->displayname .= '.eml';
} }
$attach->estimatedDataSize = $attachprops[PR_ATTACH_SIZE]; // android devices require attachment size in order to display an attachment properly
if (!isset($attachprops[PR_ATTACH_SIZE])) {
$stream = mapi_openpropertytostream($mapiattach, PR_ATTACH_DATA_BIN);
$stat = mapi_stream_stat($stream);
$attach->estimatedDataSize = $stat['cb'];
}
else {
$attach->estimatedDataSize = $attachprops[PR_ATTACH_SIZE];
}
if (isset($attachprops[PR_ATTACH_CONTENT_ID]) && $attachprops[PR_ATTACH_CONTENT_ID]) if (isset($attachprops[PR_ATTACH_CONTENT_ID]) && $attachprops[PR_ATTACH_CONTENT_ID])
$attach->contentid = $attachprops[PR_ATTACH_CONTENT_ID]; $attach->contentid = $attachprops[PR_ATTACH_CONTENT_ID];
...@@ -724,7 +742,7 @@ class MAPIProvider { ...@@ -724,7 +742,7 @@ class MAPIProvider {
} }
else { else {
$attach->attsize = $attachprops[PR_ATTACH_SIZE]; $attach->attsize = $attachprops[PR_ATTACH_SIZE];
$attach->attname = $entryid.":".$row[PR_ATTACH_NUM]; $attach->attname = sprintf("%s:%s:%s", $entryid, $row[PR_ATTACH_NUM], $parentSourcekey);
if(!isset($message->attachments)) if(!isset($message->attachments))
$message->attachments = array(); $message->attachments = array();
...@@ -792,15 +810,6 @@ class MAPIProvider { ...@@ -792,15 +810,6 @@ class MAPIProvider {
$message->lastverbexecuted = Utils::GetLastVerbExecuted($message->lastverbexecuted); $message->lastverbexecuted = Utils::GetLastVerbExecuted($message->lastverbexecuted);
} }
// OL 2013 doesn't show sender and subject for signed emails because the headers are missing
if(isset($message->messageclass) && strpos($message->messageclass, "IPM.Note.SMIME.MultipartSigned") === 0 &&
isset($message->asbody->type) && $message->asbody->type == SYNC_BODYPREFERENCE_MIME) {
ZLog::Write(LOGLEVEL_DEBUG, "Attach the transport message headers to a signed message");
$transportHeaders = array(PR_TRANSPORT_MESSAGE_HEADERS_W);
$messageHeaders = $this->getProps($mapimessage, $transportHeaders);
$message->asbody->data = $messageHeaders[PR_TRANSPORT_MESSAGE_HEADERS] ."\r\n\r\n" . $message->asbody->data;
}
return $message; return $message;
} }
...@@ -2375,49 +2384,35 @@ class MAPIProvider { ...@@ -2375,49 +2384,35 @@ class MAPIProvider {
* @return boolean * @return boolean
*/ */
private function imtoinet($mapimessage, &$message) { private function imtoinet($mapimessage, &$message) {
// if it is a signed message get a full attachment generated by ZCP if (function_exists("mapi_inetmapi_imtoinet")) {
$props = mapi_getprops($mapimessage, array(PR_MESSAGE_CLASS));
if (isset($props[PR_MESSAGE_CLASS]) && $props[PR_MESSAGE_CLASS] && strpos(strtolower($props[PR_MESSAGE_CLASS]), 'multipartsigned')) {
// find the required attachment
$attachtable = mapi_message_getattachmenttable($mapimessage);
mapi_table_restrict($attachtable, MAPIUtils::GetSignedAttachmentRestriction());
if (mapi_table_getrowcount($attachtable) == 1) {
$rows = mapi_table_queryrows($attachtable, array(PR_ATTACH_NUM, PR_ATTACH_SIZE), 0, 1);
if (isset($rows[0][PR_ATTACH_NUM])) {
$mapiattach = mapi_message_openattach($mapimessage, $rows[0][PR_ATTACH_NUM]);
$stream = mapi_openpropertytostream($mapiattach, PR_ATTACH_DATA_BIN);
$streamsize = $rows[0][PR_ATTACH_SIZE];
}
}
}
elseif (function_exists("mapi_inetmapi_imtoinet")) {
$addrbook = $this->getAddressbook(); $addrbook = $this->getAddressbook();
$stream = mapi_inetmapi_imtoinet($this->session, $addrbook, $mapimessage, array('use_tnef' => -1)); $stream = mapi_inetmapi_imtoinet($this->session, $addrbook, $mapimessage, array('use_tnef' => -1));
$mstreamstat = mapi_stream_stat($stream); $mstreamstat = mapi_stream_stat($stream);
$streamsize = $mstreamstat["cb"]; $streamsize = $mstreamstat["cb"];
}
if (isset($stream) && isset($streamsize)) { if (isset($stream) && isset($streamsize)) {
if (Request::GetProtocolVersion() >= 12.0) { if (Request::GetProtocolVersion() >= 12.0) {
if (!isset($message->asbody)) if (!isset($message->asbody))
$message->asbody = new SyncBaseBody(); $message->asbody = new SyncBaseBody();
//TODO data should be wrapped in a MapiStreamWrapper //TODO data should be wrapped in a MapiStreamWrapper
$message->asbody->data = mapi_stream_read($stream, $streamsize); $message->asbody->data = mapi_stream_read($stream, $streamsize);
$message->asbody->estimatedDataSize = $streamsize; $message->asbody->estimatedDataSize = $streamsize;
$message->asbody->truncated = 0; $message->asbody->truncated = 0;
}
else {
$message->mimetruncated = 0;
//TODO mimedata should be a wrapped in a MapiStreamWrapper
$message->mimedata = mapi_stream_read($stream, $streamsize);
$message->mimesize = $streamsize;
}
unset($message->body, $message->bodytruncated);
return true;
} }
else { else {
$message->mimetruncated = 0; ZLog::Write(LOGLEVEL_ERROR, sprintf("Error opening attachment for imtoinet"));
//TODO mimedata should be a wrapped in a MapiStreamWrapper
$message->mimedata = mapi_stream_read($stream, $streamsize);
$message->mimesize = $streamsize;
} }
unset($message->body, $message->bodytruncated);
return true;
}
else {
ZLog::Write(LOGLEVEL_ERROR, sprintf("Error opening attachment for imtoinet"));
} }
return false; return false;
} }
......
...@@ -96,6 +96,27 @@ class MAPIStreamWrapper { ...@@ -96,6 +96,27 @@ class MAPIStreamWrapper {
return $data; return $data;
} }
/**
* Stream "seek" functionality.
*
* @param int $offset
* @param int $whence
* @return boolean
*/
public function stream_seek($offset, $whence = SEEK_SET) {
switch($whence) {
case SEEK_SET:
$mapiWhence = STREAM_SEEK_SET;
break;
case SEEK_END:
$mapiWhence = STREAM_SEEK_END;
break;
default:
$mapiWhence = STREAM_SEEK_CUR;
}
return mapi_stream_seek($this->mapistream, $offset, $mapiWhence);
}
/** /**
* Returns the current position on stream * Returns the current position on stream
* *
......
...@@ -582,6 +582,45 @@ class MAPIUtils { ...@@ -582,6 +582,45 @@ class MAPIUtils {
} }
return MAPI_E_NOT_FOUND; return MAPI_E_NOT_FOUND;
} }
/**
* Function will be used to decode smime messages and convert it to normal messages.
*
* @param MAPISession $session
* @param MAPIStore $store
* @param MAPIAdressBook $addressBook
* @param MAPIMessage $message smime message
*
* @access public
* @return void
*/
public static function ParseSmime($session, $store, $addressBook, &$mapimessage) {
$props = mapi_getprops($mapimessage, array(PR_MESSAGE_CLASS));
if (isset($props[PR_MESSAGE_CLASS]) && stripos($props[PR_MESSAGE_CLASS], 'IPM.Note.SMIME.MultipartSigned') !== false) {
// this is a signed message. decode it.
$attachTable = mapi_message_getattachmenttable($mapimessage);
$rows = mapi_table_queryallrows($attachTable, array(PR_ATTACH_MIME_TAG, PR_ATTACH_NUM));
$attnum = false;
foreach($rows as $row) {
if (isset($row[PR_ATTACH_MIME_TAG]) && $row[PR_ATTACH_MIME_TAG] == 'multipart/signed') {
$attnum = $row[PR_ATTACH_NUM];
}
}
if ($attnum !== false) {
$att = mapi_message_openattach($mapimessage, $attnum);
$data = mapi_openproperty($att, PR_ATTACH_DATA_BIN);
mapi_message_deleteattach($mapimessage, $attnum);
mapi_inetmapi_imtomapi($session, $store, $addressBook, $mapimessage, $data, array("parse_smime_signed" => 1));
ZLog::Write(LOGLEVEL_DEBUG, "Convert a smime signed message to a normal message.");
}
mapi_setprops($mapimessage, array(PR_MESSAGE_CLASS => 'IPM.Note.SMIME.MultipartSigned'));
}
// TODO check if we need to do this for encrypted (and signed?) message as well
}
} }
?> ?>
\ No newline at end of file
...@@ -510,10 +510,25 @@ class BackendZarafa implements IBackend, ISearchProvider { ...@@ -510,10 +510,25 @@ class BackendZarafa implements IBackend, ISearchProvider {
if(!isset($fwmessage) || !$fwmessage) if(!isset($fwmessage) || !$fwmessage)
throw new StatusException(sprintf("ZarafaBackend->SendMail(): Could not open message id '%s' in folder id '%s' to be replied/forwarded: 0x%X", $sm->source->itemid, $sm->source->folderid, mapi_last_hresult()), SYNC_COMMONSTATUS_ITEMNOTFOUND); throw new StatusException(sprintf("ZarafaBackend->SendMail(): Could not open message id '%s' in folder id '%s' to be replied/forwarded: 0x%X", $sm->source->itemid, $sm->source->folderid, mapi_last_hresult()), SYNC_COMMONSTATUS_ITEMNOTFOUND);
//update icon when forwarding or replying message // update icon and last_verb when forwarding or replying message
if ($sm->forwardflag) mapi_setprops($fwmessage, array(PR_ICON_INDEX=>262)); // reply-all (verb 103) is not supported, as we cannot really detect this case
elseif ($sm->replyflag) mapi_setprops($fwmessage, array(PR_ICON_INDEX=>261)); if ($sm->forwardflag) {
mapi_savechanges($fwmessage); $updateProps = array(
PR_ICON_INDEX => 262,
PR_LAST_VERB_EXECUTED => 104,
);
}
elseif ($sm->replyflag) {
$updateProps = array(
PR_ICON_INDEX => 261,
PR_LAST_VERB_EXECUTED => 102,
);
}
if (isset($updateProps)) {
$updateProps[PR_LAST_VERB_EXECUTION_TIME] = time();
mapi_setprops($fwmessage, $updateProps);
mapi_savechanges($fwmessage);
}
// only attach the original message if the mobile does not send it itself // only attach the original message if the mobile does not send it itself
if (!isset($sm->replacemime)) { if (!isset($sm->replacemime)) {
...@@ -646,13 +661,17 @@ class BackendZarafa implements IBackend, ISearchProvider { ...@@ -646,13 +661,17 @@ class BackendZarafa implements IBackend, ISearchProvider {
if(!strpos($attname, ":")) if(!strpos($attname, ":"))
throw new StatusException(sprintf("ZarafaBackend->GetAttachmentData('%s'): Error, attachment requested for non-existing item", $attname), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT); throw new StatusException(sprintf("ZarafaBackend->GetAttachmentData('%s'): Error, attachment requested for non-existing item", $attname), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT);
list($id, $attachnum) = explode(":", $attname); list($id, $attachnum, $parentEntryid) = explode(":", $attname);
if (isset($parentEntryid)) {
$this->Setup(ZPush::GetAdditionalSyncFolderStore($parentEntryid));
}
$entryid = hex2bin($id); $entryid = hex2bin($id);
$message = mapi_msgstore_openentry($this->store, $entryid); $message = mapi_msgstore_openentry($this->store, $entryid);
if(!$message) if(!$message)
throw new StatusException(sprintf("ZarafaBackend->GetAttachmentData('%s'): Error, unable to open item for attachment data for id '%s' with: 0x%X", $attname, $id, mapi_last_hresult()), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT); throw new StatusException(sprintf("ZarafaBackend->GetAttachmentData('%s'): Error, unable to open item for attachment data for id '%s' with: 0x%X", $attname, $id, mapi_last_hresult()), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT);
MAPIUtils::ParseSmime($this->session, $this->defaultstore, $this->getAddressbook(), $message);
$attach = mapi_message_openattach($message, $attachnum); $attach = mapi_message_openattach($message, $attachnum);
if(!$attach) if(!$attach)
throw new StatusException(sprintf("ZarafaBackend->GetAttachmentData('%s'): Error, unable to open attachment number '%s' with: 0x%X", $attname, $attachnum, mapi_last_hresult()), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT); throw new StatusException(sprintf("ZarafaBackend->GetAttachmentData('%s'): Error, unable to open attachment number '%s' with: 0x%X", $attname, $attachnum, mapi_last_hresult()), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT);
...@@ -1128,6 +1147,11 @@ class BackendZarafa implements IBackend, ISearchProvider { ...@@ -1128,6 +1147,11 @@ class BackendZarafa implements IBackend, ISearchProvider {
*/ */
public function TerminateSearch($pid) { public function TerminateSearch($pid) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZarafaBackend->TerminateSearch(): terminating search for pid %d", $pid)); ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZarafaBackend->TerminateSearch(): terminating search for pid %d", $pid));
if (!isset($this->store) || $this->store === false) {
ZLog::Write(LOGLEVEL_WARN, sprintf("The store is not available. It is not possible to remove search folder with pid %d", $pid));
return false;
}
$storeProps = mapi_getprops($this->store, array(PR_STORE_SUPPORT_MASK, PR_FINDER_ENTRYID)); $storeProps = mapi_getprops($this->store, array(PR_STORE_SUPPORT_MASK, PR_FINDER_ENTRYID));
if (($storeProps[PR_STORE_SUPPORT_MASK] & STORE_SEARCH_OK) != STORE_SEARCH_OK) { if (($storeProps[PR_STORE_SUPPORT_MASK] & STORE_SEARCH_OK) != STORE_SEARCH_OK) {
ZLog::Write(LOGLEVEL_WARN, "Store doesn't support search folders. Public store doesn't have FINDER_ROOT folder"); ZLog::Write(LOGLEVEL_WARN, "Store doesn't support search folders. Public store doesn't have FINDER_ROOT folder");
......
...@@ -70,7 +70,6 @@ include_once('lib/core/statemanager.php'); ...@@ -70,7 +70,6 @@ include_once('lib/core/statemanager.php');
include_once('lib/core/devicemanager.php'); include_once('lib/core/devicemanager.php');
include_once('lib/core/zpush.php'); include_once('lib/core/zpush.php');
include_once('lib/core/zlog.php'); include_once('lib/core/zlog.php');
include_once('lib/core/paddingfilter.php');
include_once('lib/interface/ibackend.php'); include_once('lib/interface/ibackend.php');
include_once('lib/interface/ichanges.php'); include_once('lib/interface/ichanges.php');
include_once('lib/interface/iexportchanges.php'); include_once('lib/interface/iexportchanges.php');
......
...@@ -542,7 +542,7 @@ class ASDevice extends StateObject { ...@@ -542,7 +542,7 @@ class ASDevice extends StateObject {
if ($folderid === false) { if ($folderid === false) {
$this->hierarchyUuid = $uuid; $this->hierarchyUuid = $uuid;
// when unsetting the hierarchycache, also remove saved contentdata and ignoredmessages // when unsetting the hierarchycache, also remove saved contentdata and ignoredmessages
if ($folderid === false) { if ($folderid === false && $uuid === false) {
$this->contentData = array(); $this->contentData = array();
$this->ignoredMessageIds = array(); $this->ignoredMessageIds = array();
$this->ignoredMessages = array(); $this->ignoredMessages = array();
......
...@@ -474,13 +474,7 @@ class DeviceManager { ...@@ -474,13 +474,7 @@ class DeviceManager {
if (isset($this->windowSize[$folderid])) if (isset($this->windowSize[$folderid]))
$items = $this->windowSize[$folderid]; $items = $this->windowSize[$folderid];
else else
$items = (defined("SYNC_MAX_ITEMS")) ? SYNC_MAX_ITEMS : 100; $items = WINDOW_SIZE_MAX; // 512 by default
if (defined("SYNC_MAX_ITEMS") && SYNC_MAX_ITEMS < $items) {
if ($queuedmessages > SYNC_MAX_ITEMS)
ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->GetWindowSize() overwriting max items requested of %d by %d forced in configuration.", $items, SYNC_MAX_ITEMS));
$items = SYNC_MAX_ITEMS;
}
$this->setLatestFolder($folderid); $this->setLatestFolder($folderid);
...@@ -563,13 +557,13 @@ class DeviceManager { ...@@ -563,13 +557,13 @@ class DeviceManager {
public function ForceFullResync() { public function ForceFullResync() {
ZLog::Write(LOGLEVEL_INFO, "Full device resync requested"); ZLog::Write(LOGLEVEL_INFO, "Full device resync requested");
// delete hierarchy states
StateManager::UnLinkState($this->device, false);
// delete all other uuids // delete all other uuids
foreach ($this->device->GetAllFolderIds() as $folderid) foreach ($this->device->GetAllFolderIds() as $folderid)
$uuid = StateManager::UnLinkState($this->device, $folderid); $uuid = StateManager::UnLinkState($this->device, $folderid);
// delete hierarchy states
StateManager::UnLinkState($this->device, false);
return true; return true;
} }
...@@ -754,7 +748,7 @@ class DeviceManager { ...@@ -754,7 +748,7 @@ class DeviceManager {
} }
/** /**
* Returns the User Agent. This data is consolidated with data from Request::GetUserAgent() * Returns the User Agent. This data is consolidated with data from Request::GetUserAgent()
* and the data saved in the ASDevice. * and the data saved in the ASDevice.
* *
* @access public * @access public
......
...@@ -515,6 +515,15 @@ class LoopDetection extends InterProcessData { ...@@ -515,6 +515,15 @@ class LoopDetection extends InterProcessData {
$this->checkArrayStructure($loopdata, $folderid); $this->checkArrayStructure($loopdata, $folderid);
$current = $loopdata[self::$devid][self::$user][$folderid]; $current = $loopdata[self::$devid][self::$user][$folderid];
if (!isset($current["uuid"])) {
$current["uuid"] = $uuid;
}
if (!isset($current["count"])) {
$current["count"] = $counter;
}
if (!isset($current["queued"])) {
$current["queued"] = 0;
}
// update the usage flag // update the usage flag
$current["usage"] = $counter; $current["usage"] = $counter;
...@@ -563,15 +572,23 @@ class LoopDetection extends InterProcessData { ...@@ -563,15 +572,23 @@ class LoopDetection extends InterProcessData {
$obsolete = true; $obsolete = true;
} }
else { else {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->IsSyncStateObsolete(): check uuid counter: %d - last known counter: %d with %d queued objects", $counter, $current["count"], $current["queued"])); ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->IsSyncStateObsolete(): check folderid: '%s' uuid '%s' counter: %d - last counter: %d with %d queued",
$folderid, $uuid, $counter, $current["count"], $current["queued"]));
if ($current["uuid"] == $uuid && ($current["count"] > $counter || ($current["count"] == $counter && $current["queued"] > 0) || (isset($current["usage"]) && $current["usage"] >= $counter))) {
if ($current["uuid"] == $uuid && (
$current["count"] > $counter ||
($current["count"] == $counter && $current["queued"] > 0) ||
(isset($current["usage"]) && $current["usage"] >= $counter)
)) {
$usage = isset($current["usage"]) ? sprintf(" - counter %d already expired",$current["usage"]) : ""; $usage = isset($current["usage"]) ? sprintf(" - counter %d already expired",$current["usage"]) : "";
ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->IsSyncStateObsolete(): yes, counter already processed". $usage); ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->IsSyncStateObsolete(): yes, counter already processed". $usage);
$obsolete = true; $obsolete = true;
} }
} }
} }
else {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->IsSyncStateObsolete(): check folderid: '%s' uuid '%s' counter: %d - no data found: not obsolete", $folderid, $uuid, $counter));
}
} }
return $obsolete; return $obsolete;
...@@ -609,6 +626,7 @@ class LoopDetection extends InterProcessData { ...@@ -609,6 +626,7 @@ class LoopDetection extends InterProcessData {
* @return boolean when returning true if a loop has been identified * @return boolean when returning true if a loop has been identified
*/ */
public function Detect($folderid, $type, $uuid, $counter, $maxItems, $queuedMessages) { public function Detect($folderid, $type, $uuid, $counter, $maxItems, $queuedMessages) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->Detect(): folderid:'%s' type:'%s' uuid:'%s' counter:'%s' max:'%s' queued:'%s'", $folderid, $type, $uuid, $counter, $maxItems, $queuedMessages));
$this->broken_message_uuid = $uuid; $this->broken_message_uuid = $uuid;
$this->broken_message_counter = $counter; $this->broken_message_counter = $counter;
...@@ -636,8 +654,14 @@ class LoopDetection extends InterProcessData { ...@@ -636,8 +654,14 @@ class LoopDetection extends InterProcessData {
if (empty($current)) if (empty($current))
$current = array("type" => $type, "uuid" => $uuid, "count" => $counter-1, "queued" => $queuedMessages); $current = array("type" => $type, "uuid" => $uuid, "count" => $counter-1, "queued" => $queuedMessages);
// if data was created by SetSyncStateUsage(), we need to set the type
// TODO ZP-741: type is not used anywhere and could be removed from the loop data
if (isset($current['uuid']) && $current['uuid'] == $uuid && !isset($current['type'])) {
$current['type'] = $type;
}
// old UUID in cache - the device requested a new state!! // old UUID in cache - the device requested a new state!!
else if (isset($current['type']) && $current['type'] == $type && isset($current['uuid']) && $current['uuid'] != $uuid ) { if (isset($current['type']) && $current['type'] == $type && isset($current['uuid']) && $current['uuid'] != $uuid ) {
ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): UUID changed for folder"); ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): UUID changed for folder");
// some devices (iPhones) may request new UUIDs after broken items were sent several times // some devices (iPhones) may request new UUIDs after broken items were sent several times
...@@ -682,6 +706,7 @@ class LoopDetection extends InterProcessData { ...@@ -682,6 +706,7 @@ class LoopDetection extends InterProcessData {
// case 1.2.1 // case 1.2.1
// broken item not identified yet // broken item not identified yet
if (!isset($current['ignored']) && $counter < $current['maxCount']) { if (!isset($current['ignored']) && $counter < $current['maxCount']) {
$current['loopcount'] = 1;
$loop = true; // continue in loop-resolving $loop = true; // continue in loop-resolving
ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 1.2.1 detected"); ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 1.2.1 detected");
} }
......
...@@ -55,13 +55,15 @@ class Streamer implements Serializable { ...@@ -55,13 +55,15 @@ class Streamer implements Serializable {
const STREAMER_TYPE_DATE = 1; const STREAMER_TYPE_DATE = 1;
const STREAMER_TYPE_HEX = 2; const STREAMER_TYPE_HEX = 2;
const STREAMER_TYPE_DATE_DASHES = 3; const STREAMER_TYPE_DATE_DASHES = 3;
const STREAMER_TYPE_STREAM = 4; const STREAMER_TYPE_STREAM = 4; // deprecated
const STREAMER_TYPE_IGNORE = 5; const STREAMER_TYPE_IGNORE = 5;
const STREAMER_TYPE_SEND_EMPTY = 6; const STREAMER_TYPE_SEND_EMPTY = 6;
const STREAMER_TYPE_NO_CONTAINER = 7; const STREAMER_TYPE_NO_CONTAINER = 7;
const STREAMER_TYPE_COMMA_SEPARATED = 8; const STREAMER_TYPE_COMMA_SEPARATED = 8;
const STREAMER_TYPE_SEMICOLON_SEPARATED = 9; const STREAMER_TYPE_SEMICOLON_SEPARATED = 9;
const STREAMER_TYPE_MULTIPART = 10; const STREAMER_TYPE_MULTIPART = 10;
const STREAMER_TYPE_STREAM_ASBASE64 = 11;
const STREAMER_TYPE_STREAM_ASPLAIN = 12;
protected $mapping; protected $mapping;
public $flags; public $flags;
...@@ -78,6 +80,17 @@ class Streamer implements Serializable { ...@@ -78,6 +80,17 @@ class Streamer implements Serializable {
$this->flags = false; $this->flags = false;
} }
/**
* Return the streamer mapping for this object
*
* @access public
*/
public function GetMapping() {
return $this->mapping;
}
/** /**
* Decodes the WBXML from a WBXMLdecoder until we reach the same depth level of WBXML. * Decodes the WBXML from a WBXMLdecoder until we reach the same depth level of WBXML.
* This means that if there are multiple objects at this level, then only the first is * This means that if there are multiple objects at this level, then only the first is
...@@ -96,14 +109,16 @@ class Streamer implements Serializable { ...@@ -96,14 +109,16 @@ class Streamer implements Serializable {
$map = $this->mapping[$entity[EN_TAG]]; $map = $this->mapping[$entity[EN_TAG]];
if (isset($map[self::STREAMER_ARRAY])) { if (isset($map[self::STREAMER_ARRAY])) {
$this->$map[self::STREAMER_VAR] = array(); $this->$map[self::STREAMER_VAR] = array();
} else if(!isset($map[self::STREAMER_TYPE])) { }
else if (isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY) {
$this->$map[self::STREAMER_VAR] = "1";
}
else if(!isset($map[self::STREAMER_TYPE])) {
$this->$map[self::STREAMER_VAR] = ""; $this->$map[self::STREAMER_VAR] = "";
} }
else if ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE_DASHES ) { else if ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE_DASHES ) {
$this->$map[self::STREAMER_VAR] = ""; $this->$map[self::STREAMER_VAR] = "";
} }
else if (isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY)
$this->$map[self::STREAMER_VAR] = "";
continue; continue;
} }
// Found a start tag // Found a start tag
...@@ -314,23 +329,11 @@ class Streamer implements Serializable { ...@@ -314,23 +329,11 @@ class Streamer implements Serializable {
else if(isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_HEX) { else if(isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_HEX) {
$encoder->content(strtoupper(bin2hex($this->$map[self::STREAMER_VAR]))); $encoder->content(strtoupper(bin2hex($this->$map[self::STREAMER_VAR])));
} }
else if(isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM) { else if(isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM_ASPLAIN) {
//encode stream with base64 $encoder->contentStream($this->$map[self::STREAMER_VAR], false);
$stream = $this->$map[self::STREAMER_VAR]; }
$stat = fstat($stream); else if(isset($map[self::STREAMER_TYPE]) && ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM_ASBASE64 || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM)) {
// the padding size muss be calculated for the entire stream, $encoder->contentStream($this->$map[self::STREAMER_VAR], true);
// the base64 filter seems to process 8192 byte chunks correctly itself
$padding = (isset($stat['size']) && $stat['size'] > 8192) ? ($stat['size'] % 3) : 0;
$paddingfilter = stream_filter_append($stream, 'padding.'.$padding);
$base64filter = stream_filter_append($stream, 'convert.base64-encode');
$d = "";
while (!feof($stream)) {
$d .= fgets($stream, 4096);
}
$encoder->content($d);
stream_filter_remove($base64filter);
stream_filter_remove($paddingfilter);
} }
// implode comma or semicolon arrays into a string // implode comma or semicolon arrays into a string
else if(isset($map[self::STREAMER_TYPE]) && is_array($this->$map[self::STREAMER_VAR]) && else if(isset($map[self::STREAMER_TYPE]) && is_array($this->$map[self::STREAMER_VAR]) &&
......
...@@ -330,17 +330,28 @@ class SyncCollections implements Iterator { ...@@ -330,17 +330,28 @@ class SyncCollections implements Iterator {
} }
/** /**
* Returns the global window size which should be used for all collections * Returns the global window size of items to be exported in total over all
* in a case of a heartbeat and/or partial sync * requested collections.
* *
* @access public * @access public
* @return int/boolean returns 512 (max) if not set or not available * @return int/boolean returns requested windows size, 512 (max) or the
* value of config SYNC_MAX_ITEMS if it is lower
*/ */
public function GetGlobalWindowSize() { public function GetGlobalWindowSize() {
if (!isset($this->globalWindowSize)) // take the requested global windowsize or the max 512 if not defined
return 512; if (isset($this->globalWindowSize)) {
$globalWindowSize = $this->globalWindowSize;
}
else {
$globalWindowSize = WINDOW_SIZE_MAX; // 512 by default
}
if (defined("SYNC_MAX_ITEMS") && SYNC_MAX_ITEMS < $globalWindowSize) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->GetGlobalWindowSize() overwriting requested global window size of %d by %d forced in configuration.", $globalWindowSize, SYNC_MAX_ITEMS));
$globalWindowSize = SYNC_MAX_ITEMS;
}
return $this->globalWindowSize; return $globalWindowSize;
} }
/** /**
...@@ -506,19 +517,15 @@ class SyncCollections implements Iterator { ...@@ -506,19 +517,15 @@ class SyncCollections implements Iterator {
$validNotifications = false; $validNotifications = false;
foreach ($notifications as $folderid) { foreach ($notifications as $folderid) {
// ZP-631 - temporary disable checking validity of notifications // check if the notification on the folder is within our filter
// notify mobile for all received notifications if ($this->CountChange($folderid)) {
$this->changes[$folderid] = 1; ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): Notification received on folder '%s'", $folderid));
$validNotifications = true; $validNotifications = true;
// // check if the notification on the folder is within our filter $this->waitingTime = time()-$started;
// if ($this->CountChange($folderid)) { }
// ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): Notification received on folder '%s'", $folderid)); else {
// $validNotifications = true; ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): Notification received on folder '%s', but it is not relevant", $folderid));
// $this->waitingTime = time()-$started; }
// }
// else {
// ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): Notification received on folder '%s', but it is not relevant", $folderid));
// }
} }
if ($validNotifications) if ($validNotifications)
return true; return true;
...@@ -576,6 +583,13 @@ class SyncCollections implements Iterator { ...@@ -576,6 +583,13 @@ class SyncCollections implements Iterator {
private function CountChange($folderid) { private function CountChange($folderid) {
$spa = $this->GetCollection($folderid); $spa = $this->GetCollection($folderid);
// prevent ZP-623 by checking if the states have been used before, if so force a sync on this folder
if (ZPush::GetDeviceManager()->CheckHearbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter())) {
ZLog::Write(LOGLEVEL_DEBUG, "SyncCollections->CountChange(): Cannot verify changes for state as it was already used. Forcing sync of folder.");
$this->changes[$folderid] = 1;
return true;
}
// switch user store if this is a additional folder (additional true -> do not debug) // switch user store if this is a additional folder (additional true -> do not debug)
ZPush::GetBackend()->Setup(ZPush::GetAdditionalSyncFolderStore($folderid, true)); ZPush::GetBackend()->Setup(ZPush::GetAdditionalSyncFolderStore($folderid, true));
$changecount = false; $changecount = false;
......
...@@ -174,7 +174,7 @@ class ZLog { ...@@ -174,7 +174,7 @@ class ZLog {
* @return * @return
*/ */
static public function WriteEnd() { static public function WriteEnd() {
if (LOGLEVEL_DEBUG <= LOGLEVEL) { if (LOGLEVEL_DEBUG <= LOGLEVEL || (LOGLEVEL_DEBUG <= LOGUSERLEVEL && self::$userLog)) {
if (version_compare(phpversion(), '5.4.0') < 0) { if (version_compare(phpversion(), '5.4.0') < 0) {
$time_used = number_format(time() - $_SERVER["REQUEST_TIME"], 4); $time_used = number_format(time() - $_SERVER["REQUEST_TIME"], 4);
} }
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* *
* Created : 12.04.2011 * Created : 12.04.2011
* *
* Copyright 2007 - 2013 Zarafa Deutschland GmbH * Copyright 2007 - 2015 Zarafa Deutschland GmbH
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3, * it under the terms of the GNU Affero General Public License, version 3,
...@@ -620,7 +620,8 @@ class ZPush { ...@@ -620,7 +620,8 @@ class ZPush {
More information about Z-Push can be found at:<br> More information about Z-Push can be found at:<br>
<a href="http://z-push.org/">Z-Push homepage</a><br> <a href="http://z-push.org/">Z-Push homepage</a><br>
<a href="http://z-push.org/download">Z-Push download page</a><br> <a href="http://z-push.org/download">Z-Push download page</a><br>
<a href="http://jira.zarafa.com/browse/ZP">Z-Push Bugtracker and Roadmap</a><br> <a href="https://jira.z-hub.io/browse/ZP">Z-Push Bugtracker</a><br>
<a href="https://wiki.z-hub.io/display/ZP">Z-Push Wiki</a> and <a href="https://wiki.z-hub.io/display/ZP/Roadmap">Roadmap</a><br>
<br> <br>
All modifications to this sourcecode must be published and returned to the community.<br> All modifications to this sourcecode must be published and returned to the community.<br>
Please see <a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPLv3 License</a> for details.<br> Please see <a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPLv3 License</a> for details.<br>
......
...@@ -998,6 +998,7 @@ define("HTTP_CODE_401", 401); ...@@ -998,6 +998,7 @@ define("HTTP_CODE_401", 401);
define("HTTP_CODE_449", 449); define("HTTP_CODE_449", 449);
define("HTTP_CODE_500", 500); define("HTTP_CODE_500", 500);
define("WINDOW_SIZE_MAX", 512);
//logging defs //logging defs
define("LOGLEVEL_OFF", 0); define("LOGLEVEL_OFF", 0);
......
...@@ -61,7 +61,8 @@ class GetAttachment extends RequestProcessor { ...@@ -61,7 +61,8 @@ class GetAttachment extends RequestProcessor {
$stream = $attachment->data; $stream = $attachment->data;
ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleGetAttachment(): attachment stream from backend: %s", $stream)); ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleGetAttachment(): attachment stream from backend: %s", $stream));
if ($stream == null) // need to check for a resource here, as eg. feof('Error') === false and causing infinit loop in while!
if (!is_resource($stream))
throw new StatusException(sprintf("HandleGetAttachment(): No stream resource returned by backend for attachment: %s", $attname), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT); throw new StatusException(sprintf("HandleGetAttachment(): No stream resource returned by backend for attachment: %s", $attname), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT);
header("Content-Type: application/octet-stream"); header("Content-Type: application/octet-stream");
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* *
* Created : 16.02.2012 * Created : 16.02.2012
* *
* Copyright 2007 - 2013 Zarafa Deutschland GmbH * Copyright 2007 - 2013, 2015 Zarafa Deutschland GmbH
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3, * it under the terms of the GNU Affero General Public License, version 3,
...@@ -57,6 +57,7 @@ class Provisioning extends RequestProcessor { ...@@ -57,6 +57,7 @@ class Provisioning extends RequestProcessor {
$rwstatus = self::$deviceManager->GetProvisioningWipeStatus(); $rwstatus = self::$deviceManager->GetProvisioningWipeStatus();
$rwstatusWiped = false; $rwstatusWiped = false;
$deviceInfoSet = false;
// if this is a regular provisioning require that an authenticated remote user // if this is a regular provisioning require that an authenticated remote user
if ($rwstatus < SYNC_PROVISION_RWSTATUS_PENDING) { if ($rwstatus < SYNC_PROVISION_RWSTATUS_PENDING) {
...@@ -69,78 +70,105 @@ class Provisioning extends RequestProcessor { ...@@ -69,78 +70,105 @@ class Provisioning extends RequestProcessor {
if(!self::$decoder->getElementStartTag(SYNC_PROVISION_PROVISION)) if(!self::$decoder->getElementStartTag(SYNC_PROVISION_PROVISION))
return false; return false;
//handle android remote wipe. // Loop through Provision request tags. Possible are:
if (self::$decoder->getElementStartTag(SYNC_PROVISION_REMOTEWIPE)) { // - Remote Wipe
if(!self::$decoder->getElementStartTag(SYNC_PROVISION_STATUS)) // - DeviceInformation
return false; // - Policies
// Each of them should only be once per request.
$instatus = self::$decoder->getElementContent(); while(1) {
$requestName = "";
if(!self::$decoder->getElementEndTag()) if (self::$decoder->getElementStartTag(SYNC_PROVISION_REMOTEWIPE)) {
return false; $requestName = SYNC_PROVISION_REMOTEWIPE;
}
if (self::$decoder->getElementStartTag(SYNC_PROVISION_POLICIES)) {
$requestName = SYNC_PROVISION_POLICIES;
}
if (self::$decoder->getElementStartTag(SYNC_SETTINGS_DEVICEINFORMATION)) {
$requestName = SYNC_SETTINGS_DEVICEINFORMATION;
}
if(!self::$decoder->getElementEndTag()) if (!$requestName)
return false; break;
$phase2 = false; //set is available for OOF, device password and device information
$rwstatusWiped = true; switch ($requestName) {
} case SYNC_PROVISION_REMOTEWIPE:
else { if(!self::$decoder->getElementStartTag(SYNC_PROVISION_STATUS))
return false;
if(!self::$decoder->getElementStartTag(SYNC_PROVISION_POLICIES)) $instatus = self::$decoder->getElementContent();
return false;
if(!self::$decoder->getElementStartTag(SYNC_PROVISION_POLICY)) if(!self::$decoder->getElementEndTag())
return false; return false;
if(!self::$decoder->getElementStartTag(SYNC_PROVISION_POLICYTYPE)) if(!self::$decoder->getElementEndTag())
return false; return false;
$policytype = self::$decoder->getElementContent(); $phase2 = false;
if ($policytype != 'MS-WAP-Provisioning-XML' && $policytype != 'MS-EAS-Provisioning-WBXML') { $rwstatusWiped = true;
$status = SYNC_PROVISION_STATUS_SERVERERROR; //TODO check - do it after while(1) finished?
} break;
if(!self::$decoder->getElementEndTag()) //policytype
return false;
if (self::$decoder->getElementStartTag(SYNC_PROVISION_POLICYKEY)) { case SYNC_PROVISION_POLICIES:
$devpolicykey = self::$decoder->getElementContent(); if(!self::$decoder->getElementStartTag(SYNC_PROVISION_POLICY))
return false;
if(!self::$decoder->getElementEndTag()) if(!self::$decoder->getElementStartTag(SYNC_PROVISION_POLICYTYPE))
return false; return false;
if(!self::$decoder->getElementStartTag(SYNC_PROVISION_STATUS)) $policytype = self::$decoder->getElementContent();
return false; if ($policytype != 'MS-WAP-Provisioning-XML' && $policytype != 'MS-EAS-Provisioning-WBXML') {
$status = SYNC_PROVISION_STATUS_SERVERERROR;
}
if(!self::$decoder->getElementEndTag()) //policytype
return false;
$instatus = self::$decoder->getElementContent(); if (self::$decoder->getElementStartTag(SYNC_PROVISION_POLICYKEY)) {
$devpolicykey = self::$decoder->getElementContent();
if(!self::$decoder->getElementEndTag()) if(!self::$decoder->getElementEndTag())
return false; return false;
$phase2 = false; if(!self::$decoder->getElementStartTag(SYNC_PROVISION_STATUS))
} return false;
if(!self::$decoder->getElementEndTag()) //policy $instatus = self::$decoder->getElementContent();
return false;
if(!self::$decoder->getElementEndTag()) //policies if(!self::$decoder->getElementEndTag())
return false; return false;
if (self::$decoder->getElementStartTag(SYNC_PROVISION_REMOTEWIPE)) { $phase2 = false;
if(!self::$decoder->getElementStartTag(SYNC_PROVISION_STATUS)) }
return false;
$status = self::$decoder->getElementContent(); if(!self::$decoder->getElementEndTag()) //policy
return false;
if(!self::$decoder->getElementEndTag()) if(!self::$decoder->getElementEndTag()) //policies
return false; return false;
break;
if(!self::$decoder->getElementEndTag()) case SYNC_SETTINGS_DEVICEINFORMATION:
return false; // AS14.1 and later clients pass Device Information on the initial Provision request
if (!self::$decoder->getElementStartTag(SYNC_SETTINGS_SET))
return false;
$deviceInfoSet = true;
$deviceinformation = new SyncDeviceInformation();
$deviceinformation->Decode(self::$decoder);
$deviceinformation->Status = SYNC_SETTINGSSTATUS_SUCCESS;
self::$deviceManager->SaveDeviceInformation($deviceinformation);
if (!self::$decoder->getElementEndTag()) // SYNC_SETTINGS_SET
return false;
if (!self::$decoder->getElementEndTag()) // SYNC_SETTINGS_DEVICEINFORMATION
return false;
break;
$rwstatusWiped = true; default:
//TODO: a special status code needed?
ZLog::Write(LOGLEVEL_WARN, sprintf ("This property ('%s') is not allowed to be used in a provision request", $requestName));
} }
} }
if(!self::$decoder->getElementEndTag()) //provision if(!self::$decoder->getElementEndTag()) //provision
return false; return false;
...@@ -170,6 +198,14 @@ class Provisioning extends RequestProcessor { ...@@ -170,6 +198,14 @@ class Provisioning extends RequestProcessor {
self::$encoder->content($status); self::$encoder->content($status);
self::$encoder->endTag(); self::$encoder->endTag();
if ($deviceInfoSet) {
self::$encoder->startTag(SYNC_SETTINGS_DEVICEINFORMATION);
self::$encoder->startTag(SYNC_SETTINGS_STATUS);
self::$encoder->content($deviceinformation->Status);
self::$encoder->endTag(); //SYNC_SETTINGS_STATUS
self::$encoder->endTag(); //SYNC_SETTINGS_DEVICEINFORMATION
}
self::$encoder->startTag(SYNC_PROVISION_POLICIES); self::$encoder->startTag(SYNC_PROVISION_POLICIES);
self::$encoder->startTag(SYNC_PROVISION_POLICY); self::$encoder->startTag(SYNC_PROVISION_POLICY);
......
...@@ -232,8 +232,8 @@ class Sync extends RequestProcessor { ...@@ -232,8 +232,8 @@ class Sync extends RequestProcessor {
if(self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) { if(self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) {
$ws = self::$decoder->getElementContent(); $ws = self::$decoder->getElementContent();
// normalize windowsize - see ZP-477 // normalize windowsize - see ZP-477
if ($ws == 0 || $ws > 512) if ($ws == 0 || $ws > WINDOW_SIZE_MAX)
$ws = 512; $ws = WINDOW_SIZE_MAX;
$spa->SetWindowSize($ws); $spa->SetWindowSize($ws);
...@@ -427,7 +427,7 @@ class Sync extends RequestProcessor { ...@@ -427,7 +427,7 @@ class Sync extends RequestProcessor {
$clientid = false; $clientid = false;
// Get the SyncMessage if sent // Get the SyncMessage if sent
if(self::$decoder->getElementStartTag(SYNC_DATA)) { if(($el = self::$decoder->getElementStartTag(SYNC_DATA)) && ($el[EN_FLAGS] & EN_FLAGS_CONTENT)) {
$message = ZPush::getSyncObjectFromFolderClass($spa->GetContentClass()); $message = ZPush::getSyncObjectFromFolderClass($spa->GetContentClass());
$message->Decode(self::$decoder); $message->Decode(self::$decoder);
...@@ -569,11 +569,13 @@ class Sync extends RequestProcessor { ...@@ -569,11 +569,13 @@ class Sync extends RequestProcessor {
// update a few values // update a few values
foreach($sc as $folderid => $spa) { foreach($sc as $folderid => $spa) {
// manually set getchanges parameter for this collection // manually set getchanges parameter for this collection if it is synchronized
$sc->AddParameter($spa, "getchanges", true); if ($spa->HasSyncKey()) {
$sc->AddParameter($spa, "getchanges", true);
// announce WindowSize to DeviceManager // announce WindowSize to DeviceManager
self::$deviceManager->SetWindowSize($folderid, $spa->GetWindowSize()); self::$deviceManager->SetWindowSize($folderid, $spa->GetWindowSize());
}
} }
if (!$sc->HasCollections()) if (!$sc->HasCollections())
$status = SYNC_STATUS_SYNCREQUESTINCOMPLETE; $status = SYNC_STATUS_SYNCREQUESTINCOMPLETE;
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
* *
* Created : 08.11.2011 * Created : 08.11.2011
* *
* Copyright 2007 - 2013 Zarafa Deutschland GmbH * Copyright 2007 - 2013, 2015 Zarafa Deutschland GmbH
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3, * it under the terms of the GNU Affero General Public License, version 3,
...@@ -51,7 +51,7 @@ class SyncDevicePassword extends SyncObject { ...@@ -51,7 +51,7 @@ class SyncDevicePassword extends SyncObject {
public function SyncDevicePassword() { public function SyncDevicePassword() {
$mapping = array ( $mapping = array (
SYNC_SETTINGS_DEVICEPW => array ( self::STREAMER_VAR => "password"), SYNC_SETTINGS_PW => array ( self::STREAMER_VAR => "password"),
SYNC_SETTINGS_PROP_STATUS => array ( self::STREAMER_VAR => "Status", SYNC_SETTINGS_PROP_STATUS => array ( self::STREAMER_VAR => "Status",
self::STREAMER_TYPE => self::STREAMER_TYPE_IGNORE) self::STREAMER_TYPE => self::STREAMER_TYPE_IGNORE)
......
...@@ -52,7 +52,7 @@ class SyncItemOperationsAttachment extends SyncObject { ...@@ -52,7 +52,7 @@ class SyncItemOperationsAttachment extends SyncObject {
$mapping = array( $mapping = array(
SYNC_AIRSYNCBASE_CONTENTTYPE => array ( self::STREAMER_VAR => "contenttype"), SYNC_AIRSYNCBASE_CONTENTTYPE => array ( self::STREAMER_VAR => "contenttype"),
SYNC_ITEMOPERATIONS_DATA => array ( self::STREAMER_VAR => "data", SYNC_ITEMOPERATIONS_DATA => array ( self::STREAMER_VAR => "data",
self::STREAMER_TYPE => self::STREAMER_TYPE_STREAM, self::STREAMER_TYPE => self::STREAMER_TYPE_STREAM_ASBASE64,
self::STREAMER_PROP => self::STREAMER_TYPE_MULTIPART), self::STREAMER_PROP => self::STREAMER_TYPE_MULTIPART),
); );
......
...@@ -56,6 +56,7 @@ class SyncMeetingRequestRecurrence extends SyncObject { ...@@ -56,6 +56,7 @@ class SyncMeetingRequestRecurrence extends SyncObject {
public $dayofmonth; public $dayofmonth;
public $weekofmonth; public $weekofmonth;
public $monthofyear; public $monthofyear;
public $calendartype;
function SyncMeetingRequestRecurrence() { function SyncMeetingRequestRecurrence() {
$mapping = array ( $mapping = array (
...@@ -114,6 +115,10 @@ class SyncMeetingRequestRecurrence extends SyncObject { ...@@ -114,6 +115,10 @@ class SyncMeetingRequestRecurrence extends SyncObject {
self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(1,2,3,4,5,6,7,8,9,10,11,12) )), self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(1,2,3,4,5,6,7,8,9,10,11,12) )),
); );
if(Request::GetProtocolVersion() >= 14.0) {
$mapping[SYNC_POOMMAIL2_CALENDARTYPE] = array ( self::STREAMER_VAR => "calendartype");
}
parent::SyncObject($mapping); parent::SyncObject($mapping);
} }
} }
......
...@@ -56,6 +56,7 @@ class SyncRecurrence extends SyncObject { ...@@ -56,6 +56,7 @@ class SyncRecurrence extends SyncObject {
public $dayofmonth; public $dayofmonth;
public $weekofmonth; public $weekofmonth;
public $monthofyear; public $monthofyear;
public $calendartype;
function SyncRecurrence() { function SyncRecurrence() {
$mapping = array ( $mapping = array (
...@@ -114,6 +115,10 @@ class SyncRecurrence extends SyncObject { ...@@ -114,6 +115,10 @@ class SyncRecurrence extends SyncObject {
self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(1,2,3,4,5,6,7,8,9,10,11,12) )), self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(1,2,3,4,5,6,7,8,9,10,11,12) )),
); );
if(Request::GetProtocolVersion() >= 14.0) {
$mapping[SYNC_POOMCAL_CALENDARTYPE] = array ( self::STREAMER_VAR => "calendartype");
}
parent::SyncObject($mapping); parent::SyncObject($mapping);
} }
} }
......
...@@ -59,6 +59,7 @@ class SyncTaskRecurrence extends SyncObject { ...@@ -59,6 +59,7 @@ class SyncTaskRecurrence extends SyncObject {
public $monthofyear; public $monthofyear;
public $regenerate; public $regenerate;
public $deadoccur; public $deadoccur;
public $calendartype;
function SyncTaskRecurrence() { function SyncTaskRecurrence() {
$mapping = array ( $mapping = array (
...@@ -121,8 +122,12 @@ class SyncTaskRecurrence extends SyncObject { ...@@ -121,8 +122,12 @@ class SyncTaskRecurrence extends SyncObject {
// 1-12 representing the month // 1-12 representing the month
SYNC_POOMTASKS_MONTHOFYEAR => array ( self::STREAMER_VAR => "monthofyear", SYNC_POOMTASKS_MONTHOFYEAR => array ( self::STREAMER_VAR => "monthofyear",
self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(1,2,3,4,5,6,7,8,9,10,11,12) )), self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(1,2,3,4,5,6,7,8,9,10,11,12) )),
); );
if(Request::GetProtocolVersion() >= 14.0) {
$mapping[SYNC_POOMTASKS_CALENDARTYPE] = array ( self::STREAMER_VAR => "calendartype");
}
parent::SyncObject($mapping); parent::SyncObject($mapping);
} }
......
...@@ -148,4 +148,21 @@ if (!function_exists('http_response_code')) { ...@@ -148,4 +148,21 @@ if (!function_exists('http_response_code')) {
return $code; return $code;
} }
} }
if (!function_exists('memory_get_peak_usage')) {
/**
* memory_get_peak_usage is not available prior to PHP 5.2.
* This complementary function will return the value of memory_get_usage();
* @see http://php.net/manual/en/function.memory-get-usage.php
* @see http://php.net/manual/en/function.memory-get-peak-usage.php
*
* @param boolean $real_usage
*/
function memory_get_peak_usage($real_usage = false) {
ZLog::Write(LOGLEVEL_DEBUG, "memory_get_peak_usage() is not available on this system. The value of memory_get_usage() will be used.");
return memory_get_usage();
}
}
?> ?>
\ No newline at end of file
...@@ -429,6 +429,38 @@ class ZPushAdmin { ...@@ -429,6 +429,38 @@ class ZPushAdmin {
return true; return true;
} }
/**
* Removes the hierarchydata of a device of a user so it will be re-synchronizated.
*
* @param string $user user of the device
* @param string $devid device id which should be wiped
*
* @return boolean
* @access public
*/
static public function ResyncHierarchy($user, $devid) {
// load device data
$device = new ASDevice($devid, ASDevice::UNDEFINED, $user, ASDevice::UNDEFINED);
try {
$device->SetData(ZPush::GetStateMachine()->GetState($devid, IStateMachine::DEVICEDATA), false);
if ($device->IsNewDevice()) {
ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPushAdmin::ResyncHierarchy(): data of user '%s' not synchronized on device '%s'. Aborting.",$user, $devid));
return false;
}
// remove hierarchcache, but don't update the device, as the folder states are invalidated
StateManager::UnLinkState($device, false, false);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPushAdmin::ResyncHierarchy(): deleted hierarchy states of device '%s' of user '%s'", $devid, $user));
}
catch (StateNotFoundException $e) {
ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPushAdmin::ResyncHierarchy(): state for device '%s' of user '%s' can not be found or saved", $devid, $user));
return false;
}
return true;
}
/** /**
* Clears loop detection data * Clears loop detection data
* *
......
<?php <?php
/*********************************************** /***********************************************
* File : paddingfilter.php * File : replacenullcharfilter.php
* Project : Z-Push * Project : Z-Push
* Descr : Our own filter for stream padding with zero strings. * Descr : Filters null characters out of a stream.
* *
* Created : 18.07.2012 * Created : 11.09.2015
* *
* Copyright 2007 - 2013 Zarafa Deutschland GmbH * Copyright 2015 Zarafa Deutschland GmbH
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3, * it under the terms of the GNU Affero General Public License, version 3,
...@@ -41,20 +41,10 @@ ...@@ -41,20 +41,10 @@
* Consult LICENSE file for details * Consult LICENSE file for details
************************************************/ ************************************************/
/* Define our filter class class replace_nullchar_filter extends php_user_filter {
*
* Usage: stream_filter_append($stream, 'padding.X');
* where X is a number a stream will be padded to be
* multiple of (e.g. padding.3 will pad the stream
* to be multiple of 3 which is useful in base64
* encoding).
*
* */
class padding_filter extends php_user_filter {
private $padding = 4; // default padding
/** /**
* This method is called whenever data is read from or written to the attached stream * This method is called whenever data is read from or written to the attached stream.
* *
* @see php_user_filter::filter() * @see php_user_filter::filter()
* *
...@@ -69,33 +59,13 @@ class padding_filter extends php_user_filter { ...@@ -69,33 +59,13 @@ class padding_filter extends php_user_filter {
*/ */
function filter($in, $out, &$consumed, $closing) { function filter($in, $out, &$consumed, $closing) {
while ($bucket = stream_bucket_make_writeable($in)) { while ($bucket = stream_bucket_make_writeable($in)) {
if ($this->padding != 0 && $bucket->datalen < 8192) { $bucket->data = str_replace("\0", "", $bucket->data);
$bucket->data .= str_pad($bucket->data, $this->padding, 0x0); $consumed += $bucket->datalen;
}
$consumed += ($this->padding != 0 && $bucket->datalen < 8192) ? ($bucket->datalen + $this->padding) : $bucket->datalen;
stream_bucket_append($out, $bucket); stream_bucket_append($out, $bucket);
} }
return PSFS_PASS_ON; return PSFS_PASS_ON;
} }
/**
* Called when creating the filter
*
* @see php_user_filter::onCreate()
*
* @access public
* @return boolean
*/
function onCreate() {
$delim = strrpos($this->filtername, '.');
if ($delim !== false) {
$padding = substr($this->filtername, $delim + 1);
if (is_numeric($padding))
$this->padding = $padding;
}
return true;
}
} }
stream_filter_register("padding.*", "padding_filter"); stream_filter_register('replacenullchar', 'replace_nullchar_filter');
?> ?>
\ No newline at end of file
...@@ -165,12 +165,12 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -165,12 +165,12 @@ class WBXMLEncoder extends WBXMLDefs {
} }
/** /**
* Puts content on the output stack * Puts content on the output stack.
* *
* @param $content * @param string $content
* *
* @access public * @access public
* @return string * @return
*/ */
public function content($content) { public function content($content) {
// We need to filter out any \0 chars because it's the string terminator in WBXML. We currently // We need to filter out any \0 chars because it's the string terminator in WBXML. We currently
...@@ -183,6 +183,29 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -183,6 +183,29 @@ class WBXMLEncoder extends WBXMLDefs {
$this->_content($content); $this->_content($content);
} }
/**
* Puts content of a stream on the output stack.
*
* @param resource $stream
* @param boolean $asBase64 if true, the data will be encoded as base64, default: false
*
* @access public
* @return
*/
public function contentStream($stream, $asBase64 = false) {
if (!$asBase64) {
include_once('lib/wbxml/replacenullcharfilter.php');
$rnc_filter = stream_filter_append($stream, 'replacenullchar');
}
$this->_outputStack();
$this->_contentStream($stream, $asBase64);
if (!$asBase64) {
stream_filter_remove($rnc_filter);
}
}
/** /**
* Gets the value of multipart * Gets the value of multipart
* *
...@@ -269,9 +292,10 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -269,9 +292,10 @@ class WBXMLEncoder extends WBXMLDefs {
} }
/** /**
* Outputs actual data * Outputs actual data.
* *
* @access private * @access private
* @param string $content
* @return * @return
*/ */
private function _content($content) { private function _content($content) {
...@@ -280,6 +304,37 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -280,6 +304,37 @@ class WBXMLEncoder extends WBXMLDefs {
$this->outTermStr($content); $this->outTermStr($content);
} }
/**
* Outputs actual data coming from a stream, optionally encoded as base64.
*
* @access private
* @param resource $stream
* @param boolean $asBase64
* @return
*/
private function _contentStream($stream, $asBase64) {
// write full stream, including the finalizing terminator to the output stream (stuff outTermStr() would do)
$this->outByte(WBXML_STR_I);
fseek($stream, 0, SEEK_SET);
if ($asBase64) {
$out_filter = stream_filter_append($this->_out, 'convert.base64-encode');
}
$written = stream_copy_to_stream($stream, $this->_out);
if ($asBase64) {
stream_filter_remove($out_filter);
}
fwrite($this->_out, chr(0));
// data is out, do some logging
$stat = fstat($stream);
$logContent = sprintf("<<< written %d of %d bytes of %s data >>>", $written, $stat['size'], $asBase64 ? "base64 encoded":"plain");
$this->logContent($logContent);
// write the meta data also to the _outLog stream, WBXML_STR_I was already written by outByte() above
fwrite($this->_outLog, $logContent);
fwrite($this->_outLog, chr(0));
}
/** /**
* Outputs an actual end tag * Outputs an actual end tag
* *
...@@ -470,7 +525,7 @@ class WBXMLEncoder extends WBXMLDefs { ...@@ -470,7 +525,7 @@ class WBXMLEncoder extends WBXMLDefs {
/** /**
* Logs content to ZLog * Logs content to ZLog
* *
* @param $content * @param string $content
* *
* @access private * @access private
* @return * @return
......
...@@ -111,7 +111,7 @@ class WebserviceDevice { ...@@ -111,7 +111,7 @@ class WebserviceDevice {
} }
/** /**
* Marks a a device of the Request::GetGETUser() for resynchronization * Marks a device of the Request::GetGETUser() for resynchronization.
* *
* @param string $deviceId the device id * @param string $deviceId the device id
* *
...@@ -131,5 +131,29 @@ class WebserviceDevice { ...@@ -131,5 +131,29 @@ class WebserviceDevice {
ZPush::GetTopCollector()->AnnounceInformation(sprintf("Resync requested - device id '%s'", $deviceId), true); ZPush::GetTopCollector()->AnnounceInformation(sprintf("Resync requested - device id '%s'", $deviceId), true);
return true; return true;
} }
/**
* Marks a folder of a device of the Request::GetGETUser() for resynchronization.
*
* @param string $deviceId the device id
* @param string $folderId the folder id
*
* @access public
* @return boolean
* @throws SoapFault
*/
public function ResyncFolder($deviceId, $folderId) {
$deviceId = preg_replace("/[^A-Za-z0-9]/", "", $deviceId);
$folderId = preg_replace("/[^A-Za-z0-9]/", "", $folderId);
ZLog::Write(LOGLEVEL_INFO, sprintf("WebserviceDevice::ResyncFolder('%s','%s'): mark folder of a device of user '%s' for resynchronization", $deviceId, $folderId, Request::GetGETUser()));
if (! ZPushAdmin::ResyncFolder(Request::GetGETUser(), $deviceId, $folderId)) {
ZPush::GetTopCollector()->AnnounceInformation(ZLog::GetLastMessage(LOGLEVEL_ERROR), true);
throw new SoapFault("ERROR", ZLog::GetLastMessage(LOGLEVEL_ERROR));
}
ZPush::GetTopCollector()->AnnounceInformation(sprintf("Folder resync requested - device id '%s', folder id '%s", $deviceId, $folderId), true);
return true;
}
} }
?> ?>
\ No newline at end of file
...@@ -41,6 +41,6 @@ ...@@ -41,6 +41,6 @@
* Consult LICENSE file for details * Consult LICENSE file for details
************************************************/ ************************************************/
define("ZPUSH_VERSION", "2.2.5"); define("ZPUSH_VERSION", "2.2.7");
?> ?>
\ No newline at end of file
...@@ -140,12 +140,14 @@ class ZPushAdminCLI { ...@@ -140,12 +140,14 @@ class ZPushAdminCLI {
const COMMAND_SHOWLASTSYNC = 8; const COMMAND_SHOWLASTSYNC = 8;
const COMMAND_RESYNCFOLDER = 9; const COMMAND_RESYNCFOLDER = 9;
const COMMAND_FIXSTATES = 10; const COMMAND_FIXSTATES = 10;
const COMMAND_RESYNCHIERARCHY = 11;
const TYPE_OPTION_EMAIL = "email"; const TYPE_OPTION_EMAIL = "email";
const TYPE_OPTION_CALENDAR = "calendar"; const TYPE_OPTION_CALENDAR = "calendar";
const TYPE_OPTION_CONTACT = "contact"; const TYPE_OPTION_CONTACT = "contact";
const TYPE_OPTION_TASK = "task"; const TYPE_OPTION_TASK = "task";
const TYPE_OPTION_NOTE = "note"; const TYPE_OPTION_NOTE = "note";
const TYPE_OPTION_HIERARCHY = "hierarchy";
static private $command; static private $command;
static private $user = false; static private $user = false;
...@@ -164,24 +166,25 @@ class ZPushAdminCLI { ...@@ -164,24 +166,25 @@ class ZPushAdminCLI {
return "Usage:\n\tz-push-admin.php -a ACTION [options]\n\n" . return "Usage:\n\tz-push-admin.php -a ACTION [options]\n\n" .
"Parameters:\n\t-a list/wipe/remove/resync/clearloop\n\t[-u] username\n\t[-d] deviceid\n\n" . "Parameters:\n\t-a list/wipe/remove/resync/clearloop\n\t[-u] username\n\t[-d] deviceid\n\n" .
"Actions:\n" . "Actions:\n" .
"\tlist\t\t\t\t Lists all devices and synchronized users\n" . "\tlist\t\t\t\t\t Lists all devices and synchronized users\n" .
"\tlist -u USER\t\t\t Lists all devices of user USER\n" . "\tlist -u USER\t\t\t\t Lists all devices of user USER\n" .
"\tlist -d DEVICE\t\t\t Lists all users of device DEVICE\n" . "\tlist -d DEVICE\t\t\t\t Lists all users of device DEVICE\n" .
"\tlastsync\t\t\t Lists all devices and synchronized users and the last synchronization time\n" . "\tlastsync\t\t\t\t Lists all devices and synchronized users and the last synchronization time\n" .
"\twipe -u USER\t\t\t Remote wipes all devices of user USER\n" . "\twipe -u USER\t\t\t\t Remote wipes all devices of user USER\n" .
"\twipe -d DEVICE\t\t\t Remote wipes device DEVICE\n" . "\twipe -d DEVICE\t\t\t\t Remote wipes device DEVICE\n" .
"\twipe -u USER -d DEVICE\t\t Remote wipes device DEVICE of user USER\n" . "\twipe -u USER -d DEVICE\t\t\t Remote wipes device DEVICE of user USER\n" .
"\tremove -u USER\t\t\t Removes all state data of all devices of user USER\n" . "\tremove -u USER\t\t\t\t Removes all state data of all devices of user USER\n" .
"\tremove -d DEVICE\t\t Removes all state data of all users synchronized on device DEVICE\n" . "\tremove -d DEVICE\t\t\t Removes all state data of all users synchronized on device DEVICE\n" .
"\tremove -u USER -d DEVICE\t Removes all related state data of device DEVICE of user USER\n" . "\tremove -u USER -d DEVICE\t\t Removes all related state data of device DEVICE of user USER\n" .
"\tresync -u USER -d DEVICE\t Resynchronizes all data of device DEVICE of user USER\n" . "\tresync -u USER -d DEVICE\t\t Resynchronizes all data of device DEVICE of user USER\n" .
"\tresync -t TYPE \t\t\t Resynchronizes all folders of type $types for all devices and users.\n" . "\tresync -t TYPE \t\t\t\t Resynchronizes all folders of type $types for all devices and users.\n" .
"\tresync -t TYPE -u USER \t\t Resynchronizes all folders of type $types for the user USER.\n" . "\tresync -t TYPE -u USER \t\t\t Resynchronizes all folders of type $types for the user USER.\n" .
"\tresync -t TYPE -u USER -d DEVICE Resynchronizes all folders of type $types for a specified device and user.\n" . "\tresync -t TYPE -u USER -d DEVICE\t Resynchronizes all folders of type $types for a specified device and user.\n" .
"\tresync -t FOLDERID -u USER\t Resynchronize the specified folder id only. The USER should be specified for better performance.\n" . "\tresync -t FOLDERID -u USER\t\t Resynchronize the specified folder id only. The USER should be specified for better performance.\n" .
"\tclearloop\t\t\t Clears system wide loop detection data\n" . "\tresync -t hierarchy -u USER -d DEVICE\t Resynchronize the folder hierarchy data for an optional USER and optional DEVICE.\n" .
"\tclearloop -d DEVICE -u USER\t Clears all loop detection data of a device DEVICE and an optional user USER\n" . "\tclearloop\t\t\t\t Clears system wide loop detection data\n" .
"\tfixstates\t\t\t Checks the states for integrity and fixes potential issues\n" . "\tclearloop -d DEVICE -u USER\t\t Clears all loop detection data of a device DEVICE and an optional user USER\n" .
"\tfixstates\t\t\t\t Checks the states for integrity and fixes potential issues\n" .
"\n"; "\n";
} }
...@@ -243,9 +246,10 @@ class ZPushAdminCLI { ...@@ -243,9 +246,10 @@ class ZPushAdminCLI {
self::$type !== self::TYPE_OPTION_CONTACT && self::$type !== self::TYPE_OPTION_CONTACT &&
self::$type !== self::TYPE_OPTION_TASK && self::$type !== self::TYPE_OPTION_TASK &&
self::$type !== self::TYPE_OPTION_NOTE && self::$type !== self::TYPE_OPTION_NOTE &&
self::$type !== self::TYPE_OPTION_HIERARCHY &&
strlen(self::$type) !== 44) { strlen(self::$type) !== 44) {
self::$errormessage = "Wrong 'type'. Possible values are: ". self::$errormessage = "Wrong 'type'. Possible values are: ".
"'".self::TYPE_OPTION_EMAIL."', '".self::TYPE_OPTION_CALENDAR."', '".self::TYPE_OPTION_CONTACT."', '".self::TYPE_OPTION_TASK."', '".self::TYPE_OPTION_NOTE."' ". "'".self::TYPE_OPTION_EMAIL."', '".self::TYPE_OPTION_CALENDAR."', '".self::TYPE_OPTION_CONTACT."', '".self::TYPE_OPTION_TASK."', '".self::TYPE_OPTION_NOTE."', ".self::TYPE_OPTION_HIERARCHY."' ".
"or a 44 byte long folder id (as hex)."; "or a 44 byte long folder id (as hex).";
return; return;
} }
...@@ -300,6 +304,9 @@ class ZPushAdminCLI { ...@@ -300,6 +304,9 @@ class ZPushAdminCLI {
else else
self::$command = self::COMMAND_RESYNCDEVICE; self::$command = self::COMMAND_RESYNCDEVICE;
} }
else if (self::$type === self::TYPE_OPTION_HIERARCHY) {
self::$command = self::COMMAND_RESYNCHIERARCHY;
}
else { else {
self::$command = self::COMMAND_RESYNCFOLDER; self::$command = self::COMMAND_RESYNCFOLDER;
} }
...@@ -410,6 +417,18 @@ class ZPushAdminCLI { ...@@ -410,6 +417,18 @@ class ZPushAdminCLI {
self::CommandResyncFolder(); self::CommandResyncFolder();
break; break;
case self::COMMAND_RESYNCHIERARCHY:
if (self::$device == false && self::$user == false) {
echo "Are you sure you want to re-synchronize the hierarchy of all devices and users [y/N]: ";
$confirm = strtolower(trim(fgets(STDIN)));
if ( !($confirm === 'y' || $confirm === 'yes')) {
echo "Aborted!\n";
exit(1);
}
}
self::CommandResyncHierarchy();
break;
case self::COMMAND_CLEARLOOP: case self::COMMAND_CLEARLOOP:
self::CommandClearLoopDetectionData(); self::CommandClearLoopDetectionData();
break; break;
...@@ -588,6 +607,36 @@ class ZPushAdminCLI { ...@@ -588,6 +607,36 @@ class ZPushAdminCLI {
} }
/**
* Command "Resync hierarchy"
* Resyncs a folder type of a specific device/user or of all users
*
* @return
* @access public
*/
static public function CommandResyncHierarchy() {
// if no device is specified, search for all devices of a user. If user is not set, all devices are returned.
if (self::$device === false) {
$devicelist = ZPushAdmin::ListDevices(self::$user);
if (empty($devicelist)) {
echo "\tno devices/users found\n";
return true;
}
}
else
$devicelist = array(self::$device);
foreach ($devicelist as $deviceId) {
$users = ZPushAdmin::ListUsers($deviceId);
foreach ($users as $user) {
if (self::$user && self::$user != $user)
continue;
self::resyncHierarchy($deviceId, $user);
}
}
}
/** /**
* Command to clear the loop detection data * Command to clear the loop detection data
* Mobiles may enter loop detection (one-by-one synchring due to timeouts / erros). * Mobiles may enter loop detection (one-by-one synchring due to timeouts / erros).
...@@ -670,6 +719,20 @@ class ZPushAdminCLI { ...@@ -670,6 +719,20 @@ class ZPushAdminCLI {
echo sprintf("Resync of %d folders of type %s on device '%s' of user '%s': %s\n", count($folders), $type, $deviceId, $user, ($stat)?'Requested':ZLog::GetLastMessage(LOGLEVEL_ERROR)); echo sprintf("Resync of %d folders of type %s on device '%s' of user '%s': %s\n", count($folders), $type, $deviceId, $user, ($stat)?'Requested':ZLog::GetLastMessage(LOGLEVEL_ERROR));
} }
/**
* Resynchronizes the hierarchy of a device & user
*
* @param string $deviceId the id of the device
* @param string $user the user
*
* @return
* @access private
*/
static private function resyncHierarchy($deviceId, $user) {
$stat = ZPushAdmin::ResyncHierarchy($user, $deviceId);
echo sprintf("Removing hierarchy information for resync on device '%s' of user '%s': %s\n", $deviceId, $user, ($stat)?'Requested':ZLog::GetLastMessage(LOGLEVEL_ERROR));
}
/** /**
* Fixes the states for potential issues * Fixes the states for potential issues
* *
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
* *
* Created : 07.09.2011 * Created : 07.09.2011
* *
* Copyright 2007 - 2013 Zarafa Deutschland GmbH * Copyright 2007 - 2015 Zarafa Deutschland GmbH
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3, * it under the terms of the GNU Affero General Public License, version 3,
...@@ -531,14 +531,16 @@ class ZPushTop { ...@@ -531,14 +531,16 @@ class ZPushTop {
$this->status = "resetted"; $this->status = "resetted";
$this->statusexpire = $this->currenttime+2; $this->statusexpire = $this->currenttime+2;
} }
// enable/disable wide view
else if ($cmds[0] == "wide" || $cmds[0] == "w") { else if ($cmds[0] == "wide" || $cmds[0] == "w") {
$this->wide = true; $this->wide = ! $this->wide;
$this->status = "w i d e view"; $this->status = ($this->wide)?"w i d e view" : "normal view";
$this->statusexpire = $this->currenttime+2; $this->statusexpire = $this->currenttime+2;
} }
else if ($cmds[0] == "help" || $cmds[0] == "h") { else if ($cmds[0] == "help" || $cmds[0] == "h") {
$this->helpexpire = $this->currenttime+20; $this->helpexpire = $this->currenttime+20;
} }
// grep the log file
else if (($cmds[0] == "log" || $cmds[0] == "l") && isset($cmds[1]) ) { else if (($cmds[0] == "log" || $cmds[0] == "l") && isset($cmds[1]) ) {
if (!file_exists(LOGFILE)) { if (!file_exists(LOGFILE)) {
$this->status = "Logfile can not be found: ". LOGFILE; $this->status = "Logfile can not be found: ". LOGFILE;
...@@ -549,6 +551,7 @@ class ZPushTop { ...@@ -549,6 +551,7 @@ class ZPushTop {
} }
$this->statusexpire = time()+5; // it might be much "later" now $this->statusexpire = time()+5; // it might be much "later" now
} }
// tail the log file
else if (($cmds[0] == "tail" || $cmds[0] == "t")) { else if (($cmds[0] == "tail" || $cmds[0] == "t")) {
if (!file_exists(LOGFILE)) { if (!file_exists(LOGFILE)) {
$this->status = "Logfile can not be found: ". LOGFILE; $this->status = "Logfile can not be found: ". LOGFILE;
...@@ -565,6 +568,23 @@ class ZPushTop { ...@@ -565,6 +568,23 @@ class ZPushTop {
} }
$this->statusexpire = time()+5; // it might be much "later" now $this->statusexpire = time()+5; // it might be much "later" now
} }
// tail the error log file
else if (($cmds[0] == "error" || $cmds[0] == "e")) {
if (!file_exists(LOGERRORFILE)) {
$this->status = "Error logfile can not be found: ". LOGERRORFILE;
}
else {
$this->doingTail = true;
$this->scrClear();
$this->scrPrintAt(1,0,$this->scrAsBold("Press CTRL+C to return to Z-Push-Top\n\n"));
$secondary = "";
if (isset($cmds[1])) $secondary = " -n 200 | grep ".escapeshellarg($cmds[1]);
system('bash -c "tail -f '. LOGERRORFILE . $secondary . '" > `tty`');
$this->doingTail = false;
$this->status = "Returning from tail, updating data";
}
$this->statusexpire = time()+5; // it might be much "later" now
}
else if ($cmds[0] != "") { else if ($cmds[0] != "") {
$this->status = sprintf("Command '%s' unknown", $cmds[0]); $this->status = sprintf("Command '%s' unknown", $cmds[0]);
...@@ -630,6 +650,7 @@ class ZPushTop { ...@@ -630,6 +650,7 @@ class ZPushTop {
$h[] = " ".$this->scrAsBold("f:")." or ".$this->scrAsBold("filter:")."\t\tWithout a search word: resets the filter."; $h[] = " ".$this->scrAsBold("f:")." or ".$this->scrAsBold("filter:")."\t\tWithout a search word: resets the filter.";
$h[] = " ".$this->scrAsBold("l:STR")." or ".$this->scrAsBold("log:STR")."\tIssues 'less +G' on the logfile, after grepping on the optional STR."; $h[] = " ".$this->scrAsBold("l:STR")." or ".$this->scrAsBold("log:STR")."\tIssues 'less +G' on the logfile, after grepping on the optional STR.";
$h[] = " ".$this->scrAsBold("t:STR")." or ".$this->scrAsBold("tail:STR")."\tIssues 'tail -f' on the logfile, grepping for optional STR."; $h[] = " ".$this->scrAsBold("t:STR")." or ".$this->scrAsBold("tail:STR")."\tIssues 'tail -f' on the logfile, grepping for optional STR.";
$h[] = " ".$this->scrAsBold("e:STR")." or ".$this->scrAsBold("error:STR")."\tIssues 'tail -f' on the error logfile, grepping for optional STR.";
$h[] = " ".$this->scrAsBold("r")." or ".$this->scrAsBold("reset")."\t\tResets 'wide' or 'filter'."; $h[] = " ".$this->scrAsBold("r")." or ".$this->scrAsBold("reset")."\t\tResets 'wide' or 'filter'.";
$h[] = " ".$this->scrAsBold("o:")." or ".$this->scrAsBold("option:")."\t\tSets display options. Valid options specified below"; $h[] = " ".$this->scrAsBold("o:")." or ".$this->scrAsBold("option:")."\t\tSets display options. Valid options specified below";
$h[] = " ".$this->scrAsBold(" p")." or ".$this->scrAsBold("push")."\t\tLists/not lists active and open push connections."; $h[] = " ".$this->scrAsBold(" p")." or ".$this->scrAsBold("push")."\t\tLists/not lists active and open push connections.";
...@@ -671,7 +692,7 @@ class ZPushTop { ...@@ -671,7 +692,7 @@ class ZPushTop {
/** /**
* Pads and trims string * Pads and trims string
* *
* @param string $string to be trimmed/padded * @param string $str to be trimmed/padded
* @param int $size characters to be considered * @param int $size characters to be considered
* @param boolean $cutmiddle (optional) indicates where to long information should * @param boolean $cutmiddle (optional) indicates where to long information should
* be trimmed of, false means at the end * be trimmed of, false means at the end
...@@ -698,8 +719,9 @@ class ZPushTop { ...@@ -698,8 +719,9 @@ class ZPushTop {
* @return array 'width' and 'height' as keys * @return array 'width' and 'height' as keys
*/ */
private function scrGetSize() { private function scrGetSize() {
preg_match_all("/rows.([0-9]+);.columns.([0-9]+);/", strtolower(exec('stty -a | fgrep columns')), $output); $tty = strtolower(exec('stty -a | fgrep columns'));
if(sizeof($output) == 3) if (preg_match_all("/rows.([0-9]+);.columns.([0-9]+);/", $tty, $output) ||
preg_match_all("/([0-9]+).rows;.([0-9]+).columns;/", $tty, $output))
return array('width' => $output[2][0], 'height' => $output[1][0]); return array('width' => $output[2][0], 'height' => $output[1][0]);
return array('width' => 80, 'height' => 24); return array('width' => 80, 'height' => 24);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment