Overview

Packages

  • Kolab
    • Filter
    • Test

Classes

  • Dovecot_LDA
  • DropWrapper
  • EchoWrapper
  • Horde_Kolab_Filter
  • Horde_Kolab_Filter_Base
  • Horde_Kolab_Filter_Cli
  • Horde_Kolab_Filter_Cli_Parser
  • Horde_Kolab_Filter_Configuration
  • Horde_Kolab_Filter_Content
  • Horde_Kolab_Filter_Exception
  • Horde_Kolab_Filter_Exception_IoError
  • Horde_Kolab_Filter_Exception_Temporary
  • Horde_Kolab_Filter_Exception_Usage
  • Horde_Kolab_Filter_Factory
  • Horde_Kolab_Filter_Incoming
  • Horde_Kolab_Filter_Response
  • Horde_Kolab_Filter_Temporary_File
  • Horde_Kolab_Filter_Transport
  • Horde_Kolab_Filter_Transport_drop
  • Horde_Kolab_Filter_Transport_echo
  • Horde_Kolab_Filter_Transport_lda
  • Horde_Kolab_Filter_Transport_lmtp
  • Horde_Kolab_Filter_Transport_smtp
  • Horde_Kolab_Filter_Transport_stdout
  • Kolab_Filter_Outlook
  • Net_LMTP_TLS
  • StdOutWrapper

Interfaces

  • Horde_Kolab_Filter_Temporary
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * @package Kolab_Filter
  4:  */
  5: 
  6: /** Load the basic filter definition */
  7: require_once dirname(__FILE__) . '/Base.php';
  8: 
  9: /** Load the Transport library */
 10: require_once dirname(__FILE__) . '/Transport.php';
 11: 
 12: /**
 13:  * A Kolab Server filter for incoming mails that are parsed for iCal
 14:  * contents.
 15:  *
 16:  * Copyright 2004-2008 Klarälvdalens Datakonsult AB
 17:  *
 18:  * See the enclosed file COPYING for license information (LGPL). If you
 19:  * did not receive this file, see http://www.horde.org/licenses/lgpl21.
 20:  *
 21:  * @author  Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
 22:  * @author  Gunnar Wrobel <wrobel@pardus.de>
 23:  * @package Kolab_Filter
 24:  */
 25: class Horde_Kolab_Filter_Incoming extends Horde_Kolab_Filter_Base
 26: {
 27:     /**
 28:      * The application.
 29:      *
 30:      * @param Horde_Kolab_Filter
 31:      */
 32:     private $_application;
 33: 
 34:     /**
 35:      * A temporary storage place for incoming messages.
 36:      *
 37:      * @param Horde_Kolab_Filter_Temporary
 38:      */
 39:     private $_temporary;
 40: 
 41:     /**
 42:      * An array of headers to be added to the message
 43:      *
 44:      * @var array
 45:      */
 46:     var $_add_headers;
 47: 
 48:     /**
 49:      * Constructor.
 50:      *
 51:      * @param Horde_Kolab_Filter_Configuration $config      The configuration.
 52:      * @param Horde_Kolab_Filter_Temporary     $temporary   Temporary storage
 53:      *                                                      location.
 54:      * @param Horde_Kolab_Filter_Logger        $logger      The logging backend.
 55:      * @param Horde_Kolab_Filter               $application Main application.
 56:      */
 57:     public function __construct(
 58:         Horde_Kolab_Filter_Configuration $config,
 59:         Horde_Kolab_Filter_Temporary $temporary,
 60:         Horde_Log_Logger $logger,
 61:         Horde_Kolab_Filter $application
 62:     ) {
 63:         parent::__construct($config, $logger);
 64:         $this->_temporary = $temporary;
 65:         $this->_application = $application;
 66:     }
 67: 
 68:     /**
 69:      * Initialize the filter.
 70:      *
 71:      * @return NULL
 72:      */
 73:     public function init()
 74:     {
 75:         parent::init();
 76:         $this->_temporary->init();
 77:     }
 78: 
 79:     /**
 80:      * Handle the message.
 81:      *
 82:      * @param int    $inh        The file handle pointing to the message.
 83:      * @param string $transport  The name of the transport driver.
 84:      *
 85:      * @return mixed A PEAR_Error in case of an error, nothing otherwise.
 86:      */
 87:     function _parse($inh, $transport)
 88:     {
 89:         global $conf;
 90: 
 91:         if (empty($transport)) {
 92:             if (isset($conf['kolab']['filter']['delivery_backend'])) {
 93:                 $transport = $conf['kolab']['filter']['delivery_backend'];
 94:             } else {
 95:                 $transport = 'lmtp';
 96:             }
 97:         }
 98: 
 99:         $this->_tmpfh = $this->_temporary->getHandle();
100: 
101:         $ical = false;
102:         $add_headers = array();
103:         $headers_done = false;
104: 
105:         /* High speed section START */
106:         $headers_done = false;
107:         while (!feof($inh) && !$headers_done) {
108:             $buffer = fgets($inh, 8192);
109:             $line = rtrim( $buffer, "\r\n");
110:             if ($line == '') {
111:                 /* Done with headers */
112:                 $headers_done = true;
113:             } else if (preg_match('#^Content-Type: text/calendar#i', $line)) {
114:                 Horde::logMessage("Found iCal data in message", 'DEBUG');
115:                 $ical = true;
116:             } else if (preg_match('#^Message-ID: (.*)#i', $line, $regs)) {
117:                 $this->_id = $regs[1];
118:             }
119:             if (@fwrite($this->_tmpfh, $buffer) === false) {
120:                 $msg = $php_errormsg;
121:                 return PEAR::raiseError(sprintf("Error: Could not write to %s: %s",
122:                                                 $this->_tmpfile, $msg),
123:                                         OUT_LOG | EX_IOERR);
124:             }
125:         }
126: 
127:         if ($ical) {
128:             /* iCal already identified. So let's just pipe the rest of
129:              * the message through.
130:              */
131:             while (!feof($inh)) {
132:                 $buffer = fread($inh, 8192);
133:                 if (@fwrite($this->_tmpfh, $buffer) === false) {
134:                     $msg = $php_errormsg;
135:                     return PEAR::raiseError(sprintf("Error: Could not write to %s: %s",
136:                                                     $this->_tmpfile, $msg),
137:                                             OUT_LOG | EX_IOERR);
138:                 }
139:             }
140:         } else {
141:             /* No ical yet? Let's try to identify the string
142:              * "text/calendar". It's likely that we have a mime
143:              * multipart message including iCal then.
144:              */
145:             while (!feof($inh)) {
146:                 $buffer = fread($inh, 8192);
147:                 if (@fwrite($this->_tmpfh, $buffer) === false) {
148:                     $msg = $php_errormsg;
149:                     return PEAR::raiseError(sprintf("Error: Could not write to %s: %s",
150:                                                     $this->_tmpfile, $msg),
151:                                             OUT_LOG | EX_IOERR);
152:                 }
153:                 if (strpos($buffer, 'text/calendar')) {
154:                     $ical = true;
155:                 }
156:             }
157:         }
158:         /* High speed section END */
159: 
160:         if (@fclose($this->_tmpfh) === false) {
161:             $msg = $php_errormsg;
162:             return PEAR::raiseError(sprintf("Error: Failed closing %s: %s",
163:                                             $this->_tmpfile, $msg),
164:                                     OUT_LOG | EX_IOERR);
165:         }
166: 
167:         $recipients = $this->_config->getRecipients();
168: 
169:         if ($ical) {
170:             require_once 'Horde/Kolab/Resource.php';
171:             $newrecips = array();
172:             foreach ($recipients as $recip) {
173:                 if (strpos($recip, '+')) {
174:                     list($local, $rest)  = explode('+', $recip, 2);
175:                     list($rest, $domain) = explode('@', $recip, 2);
176:                     $resource = $local . '@' . $domain;
177:                 } else {
178:                     $resource = $recip;
179:                 }
180:                 Horde::logMessage(sprintf("Calling resmgr_filter(%s, %s, %s, %s)",
181:                                           $this->_fqhostname, $this->_sender,
182:                                           $resource, $this->_tmpfile), 'DEBUG');
183:                 $r = new Kolab_Resource();
184:                 $rc = $r->handleMessage($this->_fqhostname, $this->_sender,
185:                                         $resource, $this->_tmpfile);
186:                 $r->cleanup();
187:                 if (is_a($rc, 'PEAR_Error')) {
188:                     return $rc;
189:                 } else if (is_a($rc, 'Horde_Kolab_Resource_Reply')) {
190:                     $result = $this->_transportItipReply($rc);
191:                     if (is_a($result, 'PEAR_Error')) {
192:                         return $result;
193:                     }
194:                     Horde::logMessage('Successfully sent iTip reply', 'DEBUG');
195:                 } else if ($rc === true) {
196:                     $newrecips[] = $resource;
197:                 }
198:             }
199:             $recipients = $newrecips;
200:             $this->_add_headers[] = 'X-Kolab-Scheduling-Message: TRUE';
201:         } else {
202:             $this->_add_headers[] = 'X-Kolab-Scheduling-Message: FALSE';
203:         }
204: 
205:         /* Check if we still have recipients */
206:         if (empty($recipients)) {
207:             $this->_logger->debug('No recipients left.');
208:             return;
209:         } else {
210:             $result = $this->_deliver($transport, $recipients);
211:             if (is_a($result, 'PEAR_Error')) {
212:                 return $result;
213:             }
214:         }
215: 
216:         $this->_logger->debug('Filter_Incoming successfully completed.');
217:     }
218: 
219:     private function _transportItipReply(Horde_Kolab_Resource_Reply $reply)
220:     {
221:         global $conf;
222: 
223:         if (isset($conf['kolab']['filter']['itipreply'])) {
224:             $driver = $conf['kolab']['filter']['itipreply']['driver'];
225:             $host   = $conf['kolab']['filter']['itipreply']['params']['host'];
226:             $port   = $conf['kolab']['filter']['itipreply']['params']['port'];
227:         } else {
228:             $driver = 'smtp';
229:             $host   = 'localhost';
230:             $port   = 25;
231:         }
232: 
233:         $transport = Horde_Kolab_Filter_Transport::factory(
234:             $driver,
235:             array('host' => $host, 'port' => $port)
236:         );
237: 
238:         $result = $transport->start($reply->getSender(), $reply->getRecipient());
239:         if (is_a($result, 'PEAR_Error')) {
240:             return PEAR::raiseError('Unable to send iTip reply: ' . $result->getMessage(),
241:                                     OUT_LOG | EX_TEMPFAIL);
242:         }
243: 
244:         $result = $transport->data($reply->getData());
245:         if (is_a($result, 'PEAR_Error')) {
246:             return PEAR::raiseError('Unable to send iTip reply: ' . $result->getMessage(),
247:                                     OUT_LOG | EX_TEMPFAIL);
248:         }
249: 
250:         $result = $transport->end();
251:         if (is_a($result, 'PEAR_Error')) {
252:             return PEAR::raiseError('Unable to send iTip reply: ' . $result->getMessage(),
253:                                     OUT_LOG | EX_TEMPFAIL);
254:         }
255:     }
256: 
257:     /**
258:      * Deliver the message.
259:      *
260:      * @param string $transport  The name of the transport driver.
261:      *
262:      * @return mixed A PEAR_Error in case of an error, nothing otherwise.
263:      */
264:     function _deliver($transport, $recipients)
265:     {
266:         global $conf;
267: 
268:         if (isset($conf['kolab']['filter']['lmtp_host'])) {
269:             $host = $conf['kolab']['filter']['lmtp_host'];
270:         } else {
271:             $host = 'localhost';
272:         }
273:         if (isset($conf['kolab']['filter']['lmtp_port'])) {
274:             $port = $conf['kolab']['filter']['lmtp_port'];
275:         } else {
276:             $port = 2003;
277:         }
278: 
279:         // @todo: extract as separate (optional) class
280:         $userDb = $this->_application->getUserDb();
281: 
282:         $hosts = array();
283:         foreach ($recipients as $recipient) {
284:             if (strpos($recipient, '+')) {
285:                 list($local, $rest)  = explode('+', $recipient, 2);
286:                 list($rest, $domain) = explode('@', $recipient, 2);
287:                 $real_recipient = $local . '@' . $domain;
288:             } else {
289:                 $real_recipient = $recipient;
290:             }
291:             try {
292:                 //@todo: fix anonymous binding in Kolab_Server. The method name should be explicit.
293:                 $userDb->server->connectGuid();
294:                 $guid = $userDb->search->searchGuidForUidOrMail($real_recipient);
295:                 if (empty($guid)) {
296:                     throw new Horde_Kolab_Filter_Exception_Temporary(
297:                         sprintf('User %s does not exist!', $real_recipient)
298:                     );
299:                 }
300:                 $imapserver = $userDb->objects->fetch(
301:                     $guid, 'Horde_Kolab_Server_Object_Kolab_User'
302:                 )->getSingle('kolabHomeServer');
303:             } catch (Horde_Kolab_Server_Exception $e) {
304:                 //@todo: If a message made it till here than we shouldn't fail
305:                 // because the LDAP lookup fails. The safer alternative is to try the local
306:                 // delivery. LMTP should deny anyway in case the user is unknown
307:                 // (despite the initial postfix checks).
308:                 throw new Horde_Kolab_Filter_Exception_Temporary(
309:                     sprintf(
310:                         'Failed identifying IMAP host of user %s. Error was: %s',
311:                         $real_recipient, $e->getMessage()
312:                     ),
313:                     $e
314:                 );
315:             }
316:             if (!empty($imapserver)) {
317:                 $uhost = $imapserver;
318:             } else {
319:                 $uhost = $host;
320:             }
321:             $hosts[$uhost][] = $recipient;
322:         }
323: 
324:         foreach (array_keys($hosts) as $imap_host) {
325:             $params =  array('host' => $imap_host, 'port' => $port);
326:             if ($imap_host != $host) {
327:                 $params['user'] = $conf['kolab']['filter']['lmtp_user'];
328:                 $params['pass'] = $conf['kolab']['filter']['lmtp_pass'];
329:             }
330:             $transport = &Horde_Kolab_Filter_Transport::factory($transport, $params);
331: 
332:             $tmpf = $this->_temporary->getReadHandle();
333:             if (!$tmpf) {
334:                 $msg = $php_errormsg;
335:                 return PEAR::raiseError(sprintf("Error: Could not open %s for writing: %s",
336:                                                 $this->_tmpfile, $msg),
337:                                         OUT_LOG | EX_IOERR);
338:             }
339: 
340:             $result = $transport->start($this->_config->getSender(), $hosts[$imap_host]);
341:             if (is_a($result, 'PEAR_Error')) {
342:                 return $result;
343:             }
344: 
345:             $headers_done = false;
346:             while (!feof($tmpf) && !$headers_done) {
347:                 $buffer = fgets($tmpf, 8192);
348:                 if (!$headers_done && rtrim($buffer, "\r\n") == '') {
349:                     $headers_done = true;
350:                     foreach ($this->_add_headers as $h) {
351:                         $result = $transport->data("$h\r\n");
352:                         if (is_a($result, 'PEAR_Error')) {
353:                             return $result;
354:                         }
355:                     }
356:                 }
357:                 $result = $transport->data($buffer);
358:                 if (is_a($result, 'PEAR_Error')) {
359:                     return $result;
360:                 }
361:             }
362: 
363:             while (!feof($tmpf)) {
364:                 $buffer = fread($tmpf, 8192);
365:                 $len = strlen($buffer);
366: 
367:                 /* We can't tolerate that the buffer breaks the data
368:                  * between \r and \n, so we try to avoid that. The limit
369:                  * of 100 reads is to battle abuse
370:                  */
371:                 while ($buffer{$len-1} == "\r" && $len < 8192 + 100) {
372:                     $buffer .= fread($tmpf, 1);
373:                     $len++;
374:                 }
375:                 $result = $transport->data($buffer);
376:                 if (is_a($result, 'PEAR_Error')) {
377:                     return $result;
378:                 }
379:             }
380:             return $transport->end();
381:         }
382:     }
383: }
384: ?>
385: 
API documentation generated by ApiGen