Ejemplo n.º 1
0
 /**
  * Attempts to return a reference to a concrete
  * Horde_Kolab_Resource_Getfreebusy instance based on $driver.
  *
  * It will only create a new instance if no Horde_Kolab_Resource_Getfreebusy
  * instance with the same parameters currently exists.
  *
  * This method must be invoked as:
  * <code>$var = Horde_Kolab_Resource_Getfreebusy::singleton();</code>
  *
  * @param mixed $driver The type of concrete
  *                      Horde_Kolab_Resource_Getfreebusy subclass to
  *                      return.
  * @param array $params A hash containing any additional configuration or
  *                      connection parameters a subclass might need.
  *
  * @return Horde_Token The concrete Horde_Kolab_Resource_Getfreebusy
  *                      reference, or false on error.
  */
 public static function singleton($driver = null, $params = array())
 {
     global $conf;
     if (isset($GLOBALS['KOLAB_FILTER_TESTING'])) {
         $driver = 'mock';
         $params['data'] = $GLOBALS['KOLAB_FILTER_TESTING'];
     }
     if (empty($driver)) {
         if (isset($conf['freebusy']['driver'])) {
             $driver = $conf['freebusy']['driver'];
         } else {
             $driver = 'Kolab';
         }
     }
     ksort($params);
     $sig = hash('md5', serialize(array($driver, $params)));
     if (!isset(self::$_instances[$sig])) {
         self::$_instances[$sig] = Horde_Kolab_Resource_Freebusy::factory($driver, $params);
     }
     return self::$_instances[$sig];
 }
Ejemplo n.º 2
0
 function handleMessage($fqhostname, $sender, $resource, $tmpfname)
 {
     global $conf;
     $rdata = $this->_getResourceData($sender, $resource);
     if (is_a($rdata, 'PEAR_Error')) {
         return $rdata;
     } else {
         if ($rdata === false) {
             /* No data, probably not a local user */
             return true;
         } else {
             if ($rdata['homeserver'] && $rdata['homeserver'] != $fqhostname) {
                 /* Not the users homeserver, ignore */
                 return true;
             }
         }
     }
     $cn = $rdata['cn'];
     $id = $rdata['id'];
     if (isset($rdata['action'])) {
         $action = $rdata['action'];
     } else {
         // Manual is the only safe default!
         $action = RM_ACT_MANUAL;
     }
     Horde::log(sprintf('Action for %s is %s', $sender, $action), 'DEBUG');
     // Get out as early as possible if manual
     if ($action == RM_ACT_MANUAL) {
         Horde::log(sprintf('Passing through message to %s', $id), 'INFO');
         return true;
     }
     /* Get the iCalendar data (i.e. the iTip request) */
     $iCalendar =& $this->_getICal($tmpfname);
     if ($iCalendar === false) {
         // No iCal in mail
         Horde::log(sprintf('Could not parse iCalendar data, passing through to %s', $id), 'INFO');
         return true;
     }
     // Get the event details out of the iTip request
     $itip =& $iCalendar->findComponent('VEVENT');
     if ($itip === false) {
         Horde::log(sprintf('No VEVENT found in iCalendar data, passing through to %s', $id), 'INFO');
         return true;
     }
     $itip = new Horde_Kolab_Resource_Itip($itip);
     // What is the request's method? i.e. should we create a new event/cancel an
     // existing event, etc.
     $method = Horde_String::upper($iCalendar->getAttributeDefault('METHOD', $itip->getMethod()));
     // What resource are we managing?
     Horde::log(sprintf('Processing %s method for %s', $method, $id), 'DEBUG');
     // This is assumed to be constant across event creation/modification/deletipn
     $uid = $itip->getUid();
     Horde::log(sprintf('Event has UID %s', $uid), 'DEBUG');
     // Who is the organiser?
     $organiser = $itip->getOrganizer();
     Horde::log(sprintf('Request made by %s', $organiser), 'DEBUG');
     // What is the events summary?
     $summary = $itip->getSummary();
     $estart = new Horde_Kolab_Resource_Epoch($itip->getStart());
     $dtstart = $estart->getEpoch();
     $eend = new Horde_Kolab_Resource_Epoch($itip->getEnd());
     $dtend = $eend->getEpoch();
     Horde::log(sprintf('Event starts on <%s> %s and ends on <%s> %s.', $dtstart, $this->iCalDate2Kolab($dtstart), $dtend, $this->iCalDate2Kolab($dtend)), 'DEBUG');
     if ($action == RM_ACT_ALWAYS_REJECT) {
         if ($method == 'REQUEST') {
             Horde::log(sprintf('Rejecting %s method', $method), 'INFO');
             return $this->sendITipReply($cn, $resource, $itip, RM_ITIP_DECLINE, $organiser, $uid, $is_update);
         } else {
             Horde::log(sprintf('Passing through %s method for ACT_ALWAYS_REJECT policy', $method), 'INFO');
             return true;
         }
     }
     $is_update = false;
     $imap_error = false;
     $ignore = array();
     $folder = $this->_imapConnect($id);
     if (is_a($folder, 'PEAR_Error')) {
         $imap_error =& $folder;
     }
     if (!is_a($imap_error, 'PEAR_Error') && !$folder->exists()) {
         $imap_error =& PEAR::raiseError('Error, could not open calendar folder!', OUT_LOG | EX_TEMPFAIL);
     }
     if (!is_a($imap_error, 'PEAR_Error')) {
         $data = $folder->getData();
         if (is_a($data, 'PEAR_Error')) {
             $imap_error =& $data;
         }
     }
     if (is_a($imap_error, 'PEAR_Error')) {
         Horde::log(sprintf('Failed accessing IMAP calendar: %s', $folder->getMessage()), 'ERR');
         if ($action == RM_ACT_MANUAL_IF_CONFLICTS) {
             return true;
         }
     }
     switch ($method) {
         case 'REQUEST':
             if ($action == RM_ACT_MANUAL) {
                 Horde::log(sprintf('Passing through %s method', $method), 'INFO');
                 break;
             }
             if (is_a($imap_error, 'PEAR_Error') || !$data->objectUidExists($uid)) {
                 $old_uid = null;
             } else {
                 $old_uid = $uid;
                 $ignore[] = $uid;
                 $is_update = true;
             }
             /** Generate the Kolab object */
             $object = $itip->getKolabObject();
             $outofperiod = 0;
             // Don't even bother checking free/busy info if RM_ACT_ALWAYS_ACCEPT
             // is specified
             if ($action != RM_ACT_ALWAYS_ACCEPT) {
                 try {
                     require_once 'Horde/Kolab/Resource/Freebusy.php';
                     $fb = Horde_Kolab_Resource_Freebusy::singleton();
                     $vfb = $fb->get($resource);
                 } catch (Exception $e) {
                     return PEAR::raiseError($e->getMessage(), OUT_LOG | EX_UNAVAILABLE);
                 }
                 $vfbstart = $vfb->getAttributeDefault('DTSTART', 0);
                 $vfbend = $vfb->getAttributeDefault('DTEND', 0);
                 Horde::log(sprintf('Free/busy info starts on <%s> %s and ends on <%s> %s', $vfbstart, $this->iCalDate2Kolab($vfbstart), $vfbend, $this->iCalDate2Kolab($vfbend)), 'DEBUG');
                 $evfbend = new Horde_Kolab_Resource_Epoch($vfbend);
                 if ($vfbstart && $dtstart > $evfbend->getEpoch()) {
                     $outofperiod = 1;
                 } else {
                     // Check whether we are busy or not
                     $busyperiods = $vfb->getBusyPeriods();
                     Horde::log(sprintf('Busyperiods: %s', print_r($busyperiods, true)), 'DEBUG');
                     $extraparams = $vfb->getExtraParams();
                     Horde::log(sprintf('Extraparams: %s', print_r($extraparams, true)), 'DEBUG');
                     $conflict = false;
                     if (!empty($object['recurrence'])) {
                         $recurrence = new Horde_Date_Recurrence($dtstart);
                         $recurrence->fromHash($object['recurrence']);
                         $duration = $dtend - $dtstart;
                         $events = array();
                         $next_start = $vfbstart;
                         $next = $recurrence->nextActiveRecurrence($vfbstart);
                         while ($next !== false && $next->compareDate($vfbend) <= 0) {
                             $next_ts = $next->timestamp();
                             $events[$next_ts] = $next_ts + $duration;
                             $next = $recurrence->nextActiveRecurrence(array('year' => $next->year, 'month' => $next->month, 'mday' => $next->mday + 1, 'hour' => $next->hour, 'min' => $next->min, 'sec' => $next->sec));
                         }
                     } else {
                         $events = array($dtstart => $dtend);
                     }
                     foreach ($events as $dtstart => $dtend) {
                         Horde::log(sprintf('Requested event from %s to %s', strftime('%a, %d %b %Y %H:%M:%S %z', $dtstart), strftime('%a, %d %b %Y %H:%M:%S %z', $dtend)), 'DEBUG');
                         foreach ($busyperiods as $busyfrom => $busyto) {
                             if (empty($busyfrom) && empty($busyto)) {
                                 continue;
                             }
                             Horde::log(sprintf('Busy period from %s to %s', strftime('%a, %d %b %Y %H:%M:%S %z', $busyfrom), strftime('%a, %d %b %Y %H:%M:%S %z', $busyto)), 'DEBUG');
                             if (isset($extraparams[$busyfrom]['X-UID']) && in_array(base64_decode($extraparams[$busyfrom]['X-UID']), $ignore) || isset($extraparams[$busyfrom]['X-SID']) && in_array(base64_decode($extraparams[$busyfrom]['X-SID']), $ignore)) {
                                 // Ignore
                                 continue;
                             }
                             if ($busyfrom >= $dtstart && $busyfrom < $dtend || $dtstart >= $busyfrom && $dtstart < $busyto) {
                                 Horde::log('Request overlaps', 'DEBUG');
                                 $conflict = true;
                                 break;
                             }
                         }
                         if ($conflict) {
                             break;
                         }
                     }
                     if ($conflict) {
                         if ($action == RM_ACT_MANUAL_IF_CONFLICTS) {
                             //sendITipReply(RM_ITIP_TENTATIVE);
                             Horde::log('Conflict detected; Passing mail through', 'INFO');
                             return true;
                         } else {
                             if ($action == RM_ACT_REJECT_IF_CONFLICTS) {
                                 Horde::log('Conflict detected; rejecting', 'INFO');
                                 return $this->sendITipReply($cn, $id, $itip, RM_ITIP_DECLINE, $organiser, $uid, $is_update);
                             }
                         }
                     }
                 }
             }
             if (is_a($imap_error, 'PEAR_Error')) {
                 Horde::log('Could not access users calendar; rejecting', 'INFO');
                 return $this->sendITipReply($cn, $id, $itip, RM_ITIP_DECLINE, $organiser, $uid, $is_update);
             }
             // At this point there was either no conflict or RM_ACT_ALWAYS_ACCEPT
             // was specified; either way we add the new event & send an 'ACCEPT'
             // iTip reply
             Horde::log(sprintf('Adding event %s', $uid), 'INFO');
             if (!empty($conf['kolab']['filter']['simple_locks'])) {
                 if (!empty($conf['kolab']['filter']['simple_locks_timeout'])) {
                     $timeout = $conf['kolab']['filter']['simple_locks_timeout'];
                 } else {
                     $timeout = 60;
                 }
                 if (!empty($conf['kolab']['filter']['simple_locks_dir'])) {
                     $lockdir = $conf['kolab']['filter']['simple_locks_dir'];
                 } else {
                     $lockdir = Horde::getTempDir() . '/Kolab_Filter_locks';
                     if (!is_dir($lockdir)) {
                         mkdir($lockdir, 0700);
                     }
                 }
                 if (is_dir($lockdir)) {
                     $lockfile = $lockdir . '/' . $resource . '.lock';
                     $counter = 0;
                     while ($counter < $timeout && file_exists($lockfile)) {
                         sleep(1);
                         $counter++;
                     }
                     if ($counter == $timeout) {
                         Horde::log(sprintf('Lock timeout of %s seconds exceeded. Rejecting invitation.', $timeout), 'ERR');
                         return $this->sendITipReply($cn, $id, $itip, RM_ITIP_DECLINE, $organiser, $uid, $is_update);
                     }
                     $result = file_put_contents($lockfile, 'LOCKED');
                     if ($result === false) {
                         Horde::log(sprintf('Failed creating lock file %s.', $lockfile), 'ERR');
                     } else {
                         $this->lockfile = $lockfile;
                     }
                 } else {
                     Horde::log(sprintf('The lock directory %s is missing. Disabled locking.', $lockdir), 'ERR');
                 }
             }
             $itip->setAccepted($resource);
             $result = $data->save($itip->getKolabObject(), $old_uid);
             if (is_a($result, 'PEAR_Error')) {
                 $result->code = OUT_LOG | EX_UNAVAILABLE;
                 return $result;
             }
             if ($outofperiod) {
                 Horde::log('No freebusy information available', 'NOTICE');
                 return $this->sendITipReply($cn, $resource, $itip, RM_ITIP_TENTATIVE, $organiser, $uid, $is_update);
             } else {
                 return $this->sendITipReply($cn, $resource, $itip, RM_ITIP_ACCEPT, $organiser, $uid, $is_update);
             }
         case 'CANCEL':
             Horde::log(sprintf('Removing event %s', $uid), 'INFO');
             if (is_a($imap_error, 'PEAR_Error')) {
                 $body = sprintf(Horde_Kolab_Resource_Translation::t("Unable to access %s's calendar:"), $resource) . "\n\n" . $summary;
                 $subject = sprintf(Horde_Kolab_Resource_Translation::t("Error processing \"%s\""), $summary);
             } else {
                 if (!$data->objectUidExists($uid)) {
                     Horde::log(sprintf('Canceled event %s is not present in %s\'s calendar', $uid, $resource), 'WARNING');
                     $body = sprintf(Horde_Kolab_Resource_Translation::t("The following event that was canceled is not present in %s's calendar:"), $resource) . "\n\n" . $summary;
                     $subject = sprintf(Horde_Kolab_Resource_Translation::t("Error processing \"%s\""), $summary);
                 } else {
                     /**
                      * Delete the messages from IMAP
                      * Delete any old events that we updated
                      */
                     Horde::log(sprintf('Deleting %s because of cancel', $uid), 'DEBUG');
                     $result = $data->delete($uid);
                     if (is_a($result, 'PEAR_Error')) {
                         Horde::log(sprintf('Deleting %s failed with %s', $uid, $result->getMessage()), 'DEBUG');
                     }
                     $body = Horde_Kolab_Resource_Translation::t("The following event has been successfully removed:") . "\n\n" . $summary;
                     $subject = sprintf(Horde_Kolab_Resource_Translation::t("%s has been cancelled"), $summary);
                 }
             }
             Horde::log(sprintf('Sending confirmation of cancelation to %s', $organiser), 'WARNING');
             $body = new MIME_Part('text/plain', Horde_String::wrap($body, 76));
             $mime =& MIME_Message::convertMimePart($body);
             $mime->setTransferEncoding('quoted-printable');
             $mime->transferEncodeContents();
             // Build the reply headers.
             $msg_headers = new MIME_Headers();
             $msg_headers->addHeader('Date', date('r'));
             $msg_headers->addHeader('From', $resource);
             $msg_headers->addHeader('To', $organiser);
             $msg_headers->addHeader('Subject', $subject);
             $msg_headers->addMIMEHeaders($mime);
             $reply = new Horde_Kolab_Resource_Reply($resource, $organiser, $msg_headers, $mime);
             Horde::log('Successfully prepared cancellation reply', 'INFO');
             return $reply;
         default:
             // We either don't currently handle these iTip methods, or they do not
             // apply to what we're trying to accomplish here
             Horde::log(sprintf('Ignoring %s method and passing message through to %s', $method, $resource), 'INFO');
             return true;
     }
 }