This file is indexed.

/usr/share/z-push/backend/kopano/kopanochangeswrapper.php is in z-push-backend-kopano 2.3.8-2ubuntu1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
<?php
/***********************************************
 * File      :   kopanochangeswrapper.php
 * Project   :   Z-Push
 * Descr     :   This class fullfills the IImportChanges
 *               and IExportChanges interfaces.
 *               It instantiates the ReplyBackImExporter or
 *               the ICS Importer or Exporter depending on the need.
 *               The class decides only when the states are set what needs
 *               to be done. If there are states from the ReplyBackImExporter
 *               or the user lacks write permissions on the folder a
 *               ReplyBackImExporter will be initialized, else defauld ICS.
 *
 * Created   :   25.04.2016
 *
 * Copyright 2016 Zarafa Deutschland GmbH
 *
 * 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,
* as published by the Free Software Foundation.
 *
 * 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/>.
 *
 * Consult LICENSE file for details
 ************************************************/

class KopanoChangesWrapper implements IImportChanges, IExportChanges {
    const IMPORTER = 1;
    const EXPORTER = 2;

    // hold a static list of wrappers for stores & folders
    static private $wrappers = array();
    static private $backend;

    private $preparedAs;
    private $current;
    private $session;
    private $store;
    private $folderid;
    private $replyback;
    private $ownFolder;
    private $state;
    private $moveSrcState;
    private $moveDstState;

    /**
     * Sets the backend to be used by the wrappers. This is used to check for permissions.
     * The calls made are not part of IBackend, but are implemented by BackendZarafa only.
     *
     * @param IBackend $backend
     */
    static public function SetBackend($backend) {
        self::$backend = $backend;
    }

    /**
     * Gets a wrapper for a folder in a store.
     * $session needs to be set in order to create a new wrapper. If no session is set and
     * the wrapper does not already exist, false is returned.
     *
     * @param string $storeName
     * @param resource $session
     * @param resource $store
     * @param string $folderid
     * @param boolean $ownFolder
     *
     * @access public
     * @return KopanoChangesWrapper | boolean
     */
    static public function GetWrapper($storeName, $session, $store, $folderid, $ownFolder) {
        // if existing exporter is used by Ping we need to discard it so it's fully reconfigured (ZP-1169)
        if (isset(self::$wrappers[$storeName][$folderid]) && self::$wrappers[$storeName][$folderid]->hasDiscardDataFlag()) {
            ZLog::Write(LOGLEVEL_DEBUG, "KopanoChangesWrapper::GetWrapper(): Found existing notification check exporter. Reinitializing.");
            unset(self::$wrappers[$storeName][$folderid]);
        }

        // check early due to the folderstats
        if (isset(self::$wrappers[$storeName][$folderid])) {
            return self::$wrappers[$storeName][$folderid];
        }

        if (!isset(self::$wrappers[$storeName])) {
            self::$wrappers[$storeName] = array();
        }

        if (!isset(self::$wrappers[$storeName][$folderid]) && $session) {
            self::$wrappers[$storeName][$folderid] = new KopanoChangesWrapper($session, $store, $folderid, $ownFolder);
        }
        else {
            return false;
        }

        return self::$wrappers[$storeName][$folderid];
    }

    /**
     * Constructor
     *
     * @param mapisession       $session
     * @param mapistore         $store
     * @param string            $folderid
     * @param boolean           $ownFolder
     *
     * @access public
     * @throws StatusException
     */
    public function __construct($session, $store, $folderid, $ownFolder) {
        $this->preparedAs = null;
        $this->session = $session;
        $this->store = $store;
        $this->folderid = $folderid;
        $this->ownFolder = $ownFolder;

        $this->replyback = null;
        $this->current = null;
        $this->state = null;
        $this->didMove = false;
        $this->moveSrcState = false;
        $this->moveDstState = false;
    }

    /**
     * Indicates if the wrapper is requested to be an exporter or an importer.
     *
     * @param int $type     either KopanoChangesWrapper::IMPORTER or KopanoChangesWrapper::EXPORTER
     *
     * @access public
     * @return void
     */
    public function Prepare($type) {
        $this->preparedAs = $type;
    }

    /**
     * Indicates if the wrapper has a ReplyBackImExporter.
     *
     * @access public
     * @return boolean
     */
    public function HasReplyBackExporter() {
        return !! $this->replyback;
    }

    /**
     * Indicates if the ReplyBackImExporter is the im/exporter currently being wrapped.
     *
     * @access private
     * @return boolean
     */
    private function isReplyBackExporter() {
        return $this->current == $this->replyback;
    }

    /**
     * Indicates if the current IChanges object is an ICS exporter and has the BACKEND_DISCARD_DATA flag configured.
     * These are used to verify notifications e.g. in Ping.
     *
     * @access private
     * @return boolean
     */
    private function hasDiscardDataFlag() {
        if (isset($this->current) && $this->current instanceof ExportChangesICS && $this->current->HasDiscardDataFlag()) {
            return true;
        }

        return false;
    }

    /**
     * Initializes the correct importer, exporter or ReplyBackImExporter.
     * The wrapper needs to be prepared as importer or exporter before.
     * If the user lacks permissions, a ReplyBackImExporter will be instantiated and used.
     *
     * @access private
     * @return void
     * @throws StatusException, FatalNotImplementedException
     */
    private function init() {
        if ($this->preparedAs == self::IMPORTER) {
            if (!($this->current instanceof ImportChangesICS)) {
                // check if the user has permissions to import to this folderid
                if (!$this->ownFolder && !self::$backend->HasSecretaryACLs($this->store, $this->folderid)) {
                    ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoChangesWrapper->init(): Importer: missing permissions on folderid: '%s'. Working with ReplyBackImExporter.", Utils::PrintAsString($this->folderid)));
                    $this->replyback = $this->getReplyBackImExporter();
                    $this->current = $this->replyback;
                }
                else if (!empty($this->moveSrcState)) {
                    ZLog::Write(LOGLEVEL_DEBUG, "KopanoChangesWrapper->init(): Importer: Move state available. Working with ReplyBackImExporter.");
                    $this->replyback = $this->getReplyBackImExporter();
                    $this->current = $this->replyback;
                }
                // permissions ok, instanciate an ICS importer
                else {
                    $this->current = new ImportChangesICS($this->session, $this->store, hex2bin($this->folderid));
                }
            }
        }
        else if ($this->preparedAs == self::EXPORTER){
            if (!($this->current instanceof ExportChangesICS)) {
                // if there was something imported on a read-only folder, we need to reply back the changes
                $states = isset($this->state) ? $this->state->GetReplyBackState() : false;
                if ($this->replyback || !empty($states) || !empty($this->moveSrcState)) {
                    ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoChangesWrapper->init(): Exporter: read-only folder with folderid: '%s'. Working with ReplyBackImExporter.", Utils::PrintAsString($this->folderid)));
                    if (!$this->replyback) {
                        $this->replyback = $this->getReplyBackImExporter();
                    }
                    $this->current = $this->replyback;
                }
                else {
                    // check if the user has permissions to export from this folderid
                    if (!$this->ownFolder && !self::$backend->HasReadACLs($this->store, $this->folderid)) {
                        throw new StatusException(sprintf("KopanoChangesWrapper->init(): Exporter: missing read permissions on folderid: '%s'.", Utils::PrintAsString($this->folderid)), SYNC_STATUS_FOLDERHIERARCHYCHANGED);
                    }
                    $this->current = new ExportChangesICS($this->session, $this->store, hex2bin($this->folderid));
                }
            }
        }
        else {
            throw new FatalNotImplementedException("KopanoChangesWrapper->init(): KopanoChangesWrapper was not prepared as importer or exporter.");
        }
    }

    /**
     * Returns a new ReplyBackImExporter() for the wrapper.
     *
     * @access private
     * @return ReplyBackImExporter
     */
    private function getReplyBackImExporter() {
        return new ReplyBackImExporter($this->session, $this->store, hex2bin($this->folderid));
    }

    /**----------------------------------------------------------------------------------------------------------
     * IChanges
     */

    /**
     * Initializes the state and flags.
     *
     * @param string        $state
     * @param int           $flags
     *
     * @access public
     * @return boolean      status flag
     * @throws StatusException
     */
    public function Config($state, $flags = 0) {
        // if there is an ICS state, it will remain untouched in the ReplyBackState object
        $this->state = ReplyBackState::FromState($state);

        $this->init();

        $config = false;
        if ($this->isReplyBackExporter() || !empty($this->moveSrcState)) {
            $config = $this->current->Config($this->state->GetReplyBackState(), $flags);
        }
        else {
            $config = $this->current->Config($this->state->GetICSState(), $flags);
        }
        $this->current->SetMoveStates($this->moveSrcState, $this->moveDstState);
    }

    /**
     * Configures additional parameters used for content synchronization.
     *
     * @param ContentParameters         $contentparameters
     *
     * @access public
     * @return boolean
     * @throws StatusException
     */
    public function ConfigContentParameters($contentparameters) {
        $this->init();
        return $this->current->ConfigContentParameters($contentparameters);
    }

    /**
     * Reads and returns the current state.
     *
     * @access public
     * @return string
     */
    public function GetState() {
        $newState = $this->current->GetState();
        if ($this->isReplyBackExporter()) {
            $this->state->SetReplyBackState($newState);
        }
        else {
            $this->state->SetICSState($newState);
        }
        return ReplyBackState::ToState($this->state);
    }

    /**
     * Sets the states from move operations.
     * When src and dst state are set, a MOVE operation is being executed.
     *
     * @param mixed         $srcState
     * @param mixed         (opt) $dstState, default: null
     *
     * @access public
     * @return boolean
     */
    public function SetMoveStates($srcState, $dstState = null) {
        $this->moveSrcState = $srcState;
        $this->moveDstState = $dstState;
        return true;
    }

    /**
     * Gets the states of special move operations.
     *
     * @access public
     * @return array(0 => $srcState, 1 => $dstState)
     */
    public function GetMoveStates() {
        return $this->current->GetMoveStates();
    }

    /**----------------------------------------------------------------------------------------------------------
     * IImportChanges - pass everything directly through to $this->current
     */

    /**
     * Loads objects which are expected to be exported with the state.
     * Before importing/saving the actual message from the mobile, a conflict detection should be done.
     *
     * @param ContentParameters         $contentparameters
     * @param string                    $state
     *
     * @access public
     * @return boolean
     * @throws StatusException
     */
    public function LoadConflicts($contentparameters, $state) {
        return $this->current->LoadConflicts($contentparameters, $state);
    }


    /**
     * Imports a single message.
     *
     * @param string        $id
     * @param SyncObject    $message
     *
     * @access public
     * @return boolean/string               failure / id of message
     * @throws StatusException
     */
    public function ImportMessageChange($id, $message) {
        return $this->current->ImportMessageChange($id, $message);
    }

    /**
     * Imports a deletion. This may conflict if the local object has been modified.
     *
     * @param string        $id
     * @param boolean       $asSoftDelete   (opt) if true, the deletion is exported as "SoftDelete", else as "Remove" - default: false
     *
     * @access public
     * @return boolean
     */
    public function ImportMessageDeletion($id, $asSoftDelete = false) {
        return $this->current->ImportMessageDeletion($id);
    }

    /**
     * Imports a change in 'read' flag.
     * This can never conflict.
     *
     * @param string        $id
     * @param int           $flags
     *
     * @access public
     * @return boolean
     * @throws StatusException
     */
    public function ImportMessageReadFlag($id, $flags) {
        return $this->current->ImportMessageReadFlag($id, $flags);
    }

    /**
     * Imports a move of a message. This occurs when a user moves an item to another folder.
     *
     * @param string        $id
     * @param string        $newfolder      destination folder
     *
     * @access public
     * @return boolean
     * @throws StatusException
     */
    public function ImportMessageMove($id, $newfolder) {
        $this->didMove = true;
        // When we setup the $current importer, we didn't know what we needed to do, so we look only at the src folder for permissions.
        // Now the $newfolder could be read only as well. So we need to check it's permissions and then switch to a ReplyBackImExporter if it's r/o.
        if (!$this->isReplyBackExporter()) {

            // check if the user has permissions on the destination folder
            $dststore = self::$backend->GetMAPIStoreForFolderId(ZPush::GetAdditionalSyncFolderStore($newfolder), $newfolder);
            if (!self::$backend->HasSecretaryACLs($dststore, $newfolder)) {
                ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoChangesWrapper->ImportMessageMove(): destination folderid '%s' is missing permissions. Switching to ReplyBackImExporter.", Utils::PrintAsString($newfolder)));
                $this->replyback = $this->getReplyBackImExporter();
                $this->current = $this->replyback;

                $this->current->SetMoveStates($this->moveSrcState, $this->moveDstState);
                if (isset($this->state)) {
                    $this->current->Config($this->state->GetReplyBackState());
                }
            }
        }

        return $this->current->ImportMessageMove($id, $newfolder);
    }

    /**
     * Implement interfaces which are never used.
     */

    /**
     * Imports a change on a folder.
     *
     * @param object        $folder         SyncFolder
     *
     * @access public
     * @return boolean/SyncObject           status/object with the ath least the serverid of the folder set
     * @throws StatusException
     */
    public function ImportFolderChange($folder) {
        return false;
    }

    /**
     * Imports a folder deletion.
     *
     * @param SyncFolder    $folder         at least "serverid" needs to be set
     *
     * @access public
     * @return boolean/int  success/SYNC_FOLDERHIERARCHY_STATUS
     * @throws StatusException
    */
    public function ImportFolderDeletion($folder) {
        return false;
    }


    /**----------------------------------------------------------------------------------------------------------
     * IExportChanges  - pass everything directly through to $this->current
     */

    /**
     * Initializes the Exporter where changes are synchronized to.
     *
     * @param IImportChanges    $importer
     *
     * @access public
     * @return boolean
     */
    public function InitializeExporter(&$importer) {
        return $this->current->InitializeExporter($importer);
    }

    /**
     * Returns the amount of changes to be exported.
     *
     * @access public
     * @return int
     */
    public function GetChangeCount() {
        return $this->current->GetChangeCount();
    }

    /**
     * Synchronizes a change. The previously imported messages are now retrieved from the backend.
     * and sent back to the mobile.
     *
     * @access public
     * @return array
     */
    public function Synchronize() {
        return $this->current->Synchronize();
    }
}