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; } }
public function getData() { return $this->_headers->toString() . '\\r\\n\\r\\n' . $this->_body->toString(); }