This file is indexed.

/usr/share/horde/turba/lib/Driver/Imsp.php is in php-horde-turba 4.2.2-3.

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
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
<?php
/**
 * Turba directory driver implementation for an IMSP server.
 *
 * Copyright 2010-2014 Horde LLC (http://www.horde.org/)
 *
 * See the enclosed file LICENSE for license information (ASL).  If you did
 * did not receive this file, see http://www.horde.org/licenses/apache.
 *
 * @author   Michael Rubinsky <mrubinsk@horde.org>
 * @category Horde
 * @license  http://www.horde.org/licenses/apache ASL
 * @package  Turba
 */
class Turba_Driver_Imsp extends Turba_Driver
{
    /**
     * Horde_Imsp object
     *
     * @var Horde_Imsp
     */
    protected $_imsp;

    /**
     * The name of the addressbook.
     *
     * @var string
     */
    protected $_bookName  = '';

    /**
     * Holds if we are authenticated.
     *
     * @var boolean
     */
    protected $_authenticated = '';

    /**
     * Holds name of the field indicating an IMSP group.
     *
     * @var string
     */
    protected $_groupField = '';

    /**
     * Holds value that $_groupField will have if entry is an IMSP group.
     *
     * @var string
     */
    protected $_groupValue = '';

    /**
     * Used to set if the current search is for contacts only.
     *
     * @var boolean
     */
    protected $_noGroups = '';

    /**
     * Driver capabilities.
     *
     * @var array
     */
    protected $_capabilities = array(
        'delete_all' => true,
        'delete_addressbook' => true
    );

    /**
     * Constructs a new Turba imsp driver object.
     *
     * @param array $params  Hash containing additional configuration
     *                       parameters.
     */
    public function __construct($name = '', $params)
    {
        parent::__construct($name, $params);

        $this->params       = $params;
        $this->_groupField  = $params['group_id_field'];
        $this->_groupValue  = $params['group_id_value'];
        $this->_myRights    = $params['my_rights'];
        $this->_perms       = $this->_aclToHordePerms($params['my_rights']);
        $this->_bookName = $this->getContactOwner();

        try {
            $this->_imsp = $GLOBALS['injector']
                ->getInstance('Horde_Core_Factory_Imsp')
                ->create('Book', $this->params);
        } catch (Horde_Exception $e) {
            $this->_authenticated = false;
            throw new Turba_Exception($e);
        }
        $this->_authenticated = true;
    }

    /**
     * Returns all entries matching $critera.
     *
     * @param array $criteria  Array containing the search criteria.
     * @param array $fields    List of fields to return.
     *
     * @return array  Hash containing the search results.
     */
    protected function _search(array $criteria, array $fields, array $blobFields = array(), $count_only)
    {
        $query = $results = array();

        if (!$this->_authenticated) {
            return $query;
        }

        /* Get the search criteria. */
        if (count($criteria)) {
            foreach ($criteria as $key => $vals) {
                $names = (strval($key) == 'OR')
                    ? $this->_doSearch($vals, 'OR')
                    : $this->_doSearch($vals, 'AND');
            }
        }

        /* Now we have a list of names, get the rest. */
        $result = $this->_read('name', $names, null, $fields);
        if (is_array($result)) {
            $results = $result;
        }

        Horde::log(sprintf('IMSP returned %s results', count($results)), 'DEBUG');

        return $count_only ? count($results) : array_values($results);
    }

    /**
     * Reads the given data from the address book and returns the results.
     *
     * @param string $key        The primary key field to use (always 'name'
     *                           for IMSP).
     * @param mixed $ids         The ids of the contacts to load.
     * @param string $owner      Only return contacts owned by this user.
     * @param array $fields      List of fields to return.
     * @param array $blobFields  Array of fields containing binary data.
     *
     * @return array  Hash containing the search results.
     * @throws Turba_Exception
     */
    protected function _read($key, $ids, $owner, array $fields,
                             array $blobFields = array())
    {
        $results = array();

        if (!$this->_authenticated) {
            return $results;
        }

        $ids = array_values($ids);
        $idCount = count($ids);
        $IMSPGroups = $members = $tmembers = array();

        for ($i = 0; $i < $idCount; ++$i) {
            $result = array();

            try {
                $temp = isset($IMSPGroups[$ids[$i]])
                    ? $IMSPGroups[$ids[$i]]
                    : $this->_imsp->getEntry($this->_bookName, $ids[$i]);
            } catch (Horde_Imsp_Exception $e) {
                continue;
            }

            $temp['fullname'] = $temp['name'];
            $isIMSPGroup = false;
            if (!isset($temp['__owner'])) {
                $temp['__owner'] = $GLOBALS['registry']->getAuth();
            }

            if ((isset($temp[$this->_groupField])) &&
                ($temp[$this->_groupField] == $this->_groupValue)) {
                if ($this->_noGroups) {
                    continue;
                }
                if (!isset($IMSPGroups[$ids[$i]])) {
                    $IMSPGroups[$ids[$i]] = $temp;
                }
                // move group ids to end of list
                if ($idCount > count($IMSPGroups) &&
                    $idCount - count($IMSPGroups) > $i) {
                    $ids[] = $ids[$i];
                    unset($ids[$i]);
                    $ids = array_values($ids);
                    --$i;
                    continue;
                }
                $isIMSPGroup = true;
            }
            // Get the group members that might have been added from other
            // IMSP applications, but only if we need more information than
            // the group name
            if ($isIMSPGroup &&
                array_search('__members', $fields) !== false) {
                if (isset($temp['email'])) {
                    $emailList = $this->_getGroupEmails($temp['email']);
                    $count = count($emailList);
                    for ($j = 0; $j < $count; ++$j) {
                        $needMember = true;
                        foreach ($results as $curResult) {
                            if (!empty($curResult['email']) &&
                                strtolower($emailList[$j]) == strtolower(trim($curResult['email']))) {
                                $members[] = $curResult['name'];
                                $needMember = false;
                            }
                        }
                        if ($needMember) {
                            $memberName = $this->_imsp->search
                                ($this->_bookName,
                                 array('email' => trim($emailList[$j])));

                            if (count($memberName)) {
                                $members[] = $memberName[0];
                            }
                        }
                    }
                }
                if (!empty($temp['__members'])) {
                    $tmembers = @unserialize($temp['__members']);
                }

                // TODO: Make sure that we are using the correct naming
                // convention for members regardless of if we are using
                // shares or not. This is needed to assure groups created
                // while not using shares won't be lost when transitioning
                // to shares and visa versa.
                //$tmembers = $this->_checkMemberFormat($tmembers);

                $temp['__members'] = serialize($this->_removeDuplicated(
                                               array($members, $tmembers)));
                $temp['__type'] = 'Group';
                $temp['email'] = null;
                $result = $temp;
            } else {
                // IMSP contact.
                $count = count($fields);
                for ($j = 0; $j < $count; ++$j) {
                    if (isset($temp[$fields[$j]])) {
                        $result[$fields[$j]] = $temp[$fields[$j]];
                    }
                }
            }

            $results[] = $result;
        }

        return $results;
    }

    /**
     * Adds the specified contact to the addressbook.
     *
     * @param array $attributes  The attribute values of the contact.
     * @param array $blob_fields  Fields that represent binary data.
     * @param array $date_fields  Fields that represent dates. @since 4.2.0
     *
     * @throws Turba_Exception
     */
    protected function _add(array $attributes, array $blob_fields = array(), array $date_fields = array())
    {
        /* We need to map out Turba_Object_Groups back to IMSP groups before
         * writing out to the server. We need to array_values() it in
         * case an entry was deleted from the group. */
        if ($attributes['__type'] == 'Group') {
            /* We may have a newly created group. */
            $attributes[$this->_groupField] = $this->_groupValue;
            if (!isset($attributes['__members'])) {
                $attributes['__members'] = '';
                $attributes['email'] = ' ';
            }
            $temp = unserialize($attributes['__members']);
            if (is_array($temp)) {
                $members = array_values($temp);
            } else {
                $members = array();
            }

            // This searches the current IMSP address book to see if
            // we have a match for this member before adding to email
            // attribute since IMSP groups in other IMSP aware apps
            // generally require an existing conact entry in the current
            // address book for each group member (this is necessary for
            // those sources that may be used both in AND out of Horde).
            try {
                $result = $this->_read('name', $members, null, array('email'));
                $count = count($result);
                for ($i = 0; $i < $count; ++$i) {
                    if (isset($result[$i]['email'])) {
                        $contact = sprintf("%s<%s>\n", $members[$i],
                                           $result[$i]['email']);
                        $attributes['email'] .= $contact;
                    }
                }
            } catch (Turba_Exception $e) {}
        }

        unset($attributes['__type'], $attributes['fullname']);
        if (!$this->params['contact_ownership']) {
            unset($attributes['__owner']);
        }

        return $this->_imsp->addEntry($this->_bookName, $attributes);
    }

    /**
     * TODO
     */
    protected function _canAdd()
    {
        return true;
    }

    /**
     * Deletes the specified object from the IMSP server.
     *
     * @throws Turba_Exception
     */
    protected function _delete($object_key, $object_id)
    {
        try {
            $this->_imsp->deleteEntry($this->_bookName, $object_id);
        } catch (Horde_Imsp_Exception $e) {
            throw new Turba_Exception($e);
        }
    }

    /**
     * Deletes the address book represented by this driver from the IMSP server.
     *
     * @throws Turba_Exception
     */
     protected function _deleteAll()
     {
         try {
             $this->_imsp->deleteAddressbook($this->_bookName);
         } catch (Horde_Imsp_Exception $e) {
             throw new Turba_Exception($e);
         }
     }

    /**
     * Saves the specified object to the IMSP server.
     *
     * @param Turba_Object $object  The object to save/update.
     *
     * @return string  The object id, possibly updated.
     * @throws Turba_Exception
     */
    protected function _save($object)
    {
        list($object_key, $object_id) = each($this->toDriverKeys(array('__key' => $object->getValue('__key'))));
        $attributes = $this->toDriverKeys($object->getAttributes());

        /* Check if the key changed, because IMSP will just write out
         * a new entry without removing the previous one. */
        if ($attributes['name'] != $this->_makeKey($attributes)) {
            $this->_delete($object_key, $attributes['name']);
            $attributes['name'] = $this->_makeKey($attributes);
            $object_id = $attributes['name'];
        }

        $this->_add($attributes);

        return $object_id;
    }

    /**
     * Create an object key for a new object.
     *
     * @param array $attributes  The attributes (in driver keys) of the
     *                           object being added.
     *
     * @return string  A unique ID for the new object.
     */
    protected function _makeKey($attributes)
    {
        return $attributes['fullname'];
    }

    /**
     * Parses out $emailText into an array of pure email addresses
     * suitable for searching the IMSP datastore with.
     *
     * @param string $emailText  Single string containing email addressses.
     *
     * @return array  Pure email address.
     */
    protected function _getGroupEmails($emailText)
    {
        preg_match_all("(\w[-._\w]*\w@\w[-._\w]*\w\.\w{2,3})", $emailText, $matches);
        return $matches[0];
    }

    /**
     * Parses the search criteria, requests the individual searches from the
     * server and performs any necessary ANDs / ORs on the results.
     *
     * @param array  $criteria  Array containing the search criteria.
     * @param string $glue      Type of search to perform (AND / OR).
     *
     * @return array  Array containing contact names that match $criteria.
     */
    protected function _doSearch($criteria, $glue)
    {
        $results = array();
        foreach ($criteria as $vals) {
            if (!empty($vals['OR'])) {
                $results[] = $this->_doSearch($vals['OR'], 'OR');
            } elseif (!empty($vals['AND'])) {
                $results[] = $this->_doSearch($vals['AND'], 'AND');
            } else {
                /* If we are here, and we have a ['field'] then we
                 * must either do the 'AND' or the 'OR' search. */
                if (isset($vals['field'])) {
                    $results[] = $this->_sendSearch($vals);
                } else {
                    foreach ($vals as $test) {
                        if (!empty($test['OR'])) {
                            $results[] = $this->_doSearch($test['OR'], 'OR');
                        } elseif (!empty($test['AND'])) {
                            $results[] = $this->_doSearch($test['AND'], 'AND');
                        } else {
                            $results[] = $this->_doSearch(array($test), $glue);
                        }
                    }
                }
            }
        }

        return ($glue == 'AND')
            ? $this->_getDuplicated($results)
            : $this->_removeDuplicated($results);
    }

    /**
     * Sends a search request to the server.
     *
     * @param array $criteria  Array containing the search critera.
     *
     * @return array  Array containing a list of names that match the search.
     */
    function _sendSearch($criteria)
    {
        $names = '';
        $imspSearch = array();
        $searchkey = $criteria['field'];
        $searchval = $criteria['test'];
        $searchop = $criteria['op'];
        $hasName = false;
        $this->_noGroups = false;
        $cache = $GLOBALS['injector']->getInstance('Horde_Cache');
        $key = implode(".", array_merge($criteria, array($this->_bookName)));

        /* Now make sure we aren't searching on a dynamically created
         * field. */
        switch ($searchkey) {
        case 'fullname':
            if (!$hasName) {
                $searchkey = 'name';
                $hasName = true;
            } else {
                $searchkey = '';
            }
            break;

        case '__owner':
            if (!$this->params['contact_ownership']) {
                $searchkey = '';
                $hasName = true;
            }
            break;
        }

        /* Are we searching for only Turba_Object_Groups or Turba_Objects?
         * This is needed so the 'Show Lists' and 'Show Contacts'
         * links work correctly in Turba. */
        if ($searchkey == '__type') {
            switch ($searchval) {
            case 'Group':
                $searchkey = $this->_groupField;
                $searchval = $this->_groupValue;
                break;

            case 'Object':
                if (!$hasName) {
                    $searchkey = 'name';
                    $searchval = '';
                    $hasName = true;
                } else {
                    $searchkey = '';
                }
                $this->_noGroups = true;
                break;
            }
        }

        if (!$searchkey == '') {
            // Check $searchval for content and for strict matches.
            if (strlen($searchval) > 0) {
                if ($searchop == 'LIKE') {
                    $searchval = '*' . $searchval . '*';
                }
            } else {
                $searchval = '*';
            }
            $imspSearch[$searchkey] = $searchval;
        }
        if (!count($imspSearch)) {
            $imspSearch['name'] = '*';
        }

        /* Finally get to the command.  Check the cache first, since each
         * 'Turba' search may consist of a number of identical IMSP
         * searchaddress calls in order for the AND and OR parts to work
         * correctly.  15 Second lifetime should be reasonable for this. This
         * should reduce load on IMSP server somewhat.*/
        $results = $cache->get($key, 15);

        if ($results) {
            $names = unserialize($results);
        }

        if (!$names) {
            try {
                $names = $this->_imsp->search($this->_bookName, $imspSearch);
                $cache->set($key, serialize($names));
                return $names;
            } catch (Horde_Imsp_Exception $e) {
                $GLOBALS['notification']->push($names, 'horde.error');
            }
        } else {
            return $names;
        }
    }

    /**
     * Returns only those names that are duplicated in $names
     *
     * @param array $names  A nested array of arrays containing names
     *
     * @return array  Array containing the 'AND' of all arrays in $names
     */
    protected function _getDuplicated($names)
    {
        $matched = $results = array();

        /* If there is only 1 array, simply return it. */
        if (count($names) < 2) {
            return $names[0];
        }

        for ($i = 0; $i < count($names); ++$i) {
            if (is_array($names[$i])) {
                $results = array_merge($results, $names[$i]);
            }
        }

        $search = array_count_values($results);
        foreach ($search as $key => $value) {
            if ($value > 1) {
                $matched[] = $key;
            }
        }

        return $matched;
    }

    /**
     * Returns an array with all duplicate names removed.
     *
     * @param array $names  Nested array of arrays containing names.
     *
     * @return array  Array containg the 'OR' of all arrays in $names.
     */
    protected function _removeDuplicated($names)
    {
        $unames = array();
        for ($i = 0; $i < count($names); ++$i) {
            if (is_array($names[$i])) {
                $unames = array_merge($unames, $names[$i]);
            }
        }

        return array_unique($unames);
    }

    /**
     * Checks if the current user has the requested permission
     * on this source.
     *
     * @param integer $perm  The permission to check for.
     *
     * @return boolean  true if user has permission, false otherwise.
     */
    public function hasPermission($perm)
    {
        return $this->_perms & $perm;
    }

    /**
     * Converts an acl string to a Horde Permissions bitmask.
     *
     * @param string $acl  A standard, IMAP style acl string.
     *
     * @return integer  Horde Permissions bitmask.
     */
    protected function _aclToHordePerms($acl)
    {
        $hPerms = 0;

        if (strpos($acl, 'w') !== false) {
            $hPerms |= Horde_Perms::EDIT;
        }
        if (strpos($acl, 'r') !== false) {
            $hPerms |= Horde_Perms::READ;
        }
        if (strpos($acl, 'd') !== false) {
            $hPerms |= Horde_Perms::DELETE;
        }
        if (strpos($acl, 'l') !== false) {
            $hPerms |= Horde_Perms::SHOW;
        }

        return $hPerms;
    }

    /**
     * Creates a new Horde_Share and creates the address book
     * on the IMSP server.
     *
     * @param array  The params for the share.
     *
     * @return Horde_Share  The share object.
     * @throws Turba_Exception
     */
    public function createShare($share_id, $params)
    {
        $params['params']['name'] = $this->params['username'];
        if (!isset($params['default']) || $params['default'] !== true) {
            $params['params']['name'] .= '.' . $params['name'];
        }

        $result = Turba::createShare($share_id, $params);
        try {
            Horde_Core_Imsp_Utils::createBook($GLOBALS['cfgSources']['imsp'], $params['params']['name']);
        } catch (Horde_Imsp_Exception $e) {
            throw new Turba_Exception($e);
        }

        return $result;
    }

    /**
     * Helper function to count the occurances of the ':' * delimiter in group
     * member entries.
     *
     * @param string $in  The group member entry.
     *
     * @return integer  The number of ':' in $in.
     */
    protected function _countDelimiters($in)
    {
        $cnt = $pos = 0;
        while (($pos = strpos($in, ':', $pos + 1)) !== false) {
            ++$cnt;
        }

        return $cnt;
    }

    /**
     * Returns the owner for this contact. For an IMSP source, this should be
     * the name of the address book.
     *
     * @return string  TODO
     */
    protected function _getContactOwner()
    {
       return $this->params['name'];
    }

    /**
     * Check if the passed in share is the default share for this source.
     *
     * @see turba/lib/Turba_Driver#checkDefaultShare($share, $srcconfig)
     *
     * @return TODO
     */
    public function checkDefaultShare($share, $srcConfig)
    {
        $params = @unserialize($share->get('params'));
        if (!isset($params['default'])) {
            $params['default'] = ($params['name'] == $srcConfig['params']['username']);
            $share->set('params', serialize($params));
            $share->save();
        }

        return $params['default'];
    }

}