gunnar: server/php-kolab/Kolab_Filter/Filter kolabmailtransport.php, NONE, 1.1 misc.php, NONE, 1.1 olhacks.php, NONE, 1.1 resmgr.php, NONE, 1.1

cvs at kolab.org cvs at kolab.org
Wed Aug 8 08:51:18 CEST 2007


Author: gunnar

Update of /kolabrepository/server/php-kolab/Kolab_Filter/Filter
In directory doto:/tmp/cvs-serv29057/Filter

Added Files:
	kolabmailtransport.php misc.php olhacks.php resmgr.php 
Log Message:
A second PEAR package derived from kolab-resource-handlers/resmgr. Not yet completed.

--- NEW FILE: kolabmailtransport.php ---
<?php
/*
 *  Copyright (c) 2005 Klaraelvdalens Datakonsult AB
 *
 *    Written by Steffen Hansen <steffen at klaralvdalens-datakonsult.se>
 *
 *  This  program is free  software; you can redistribute  it and/or
 *  modify it  under the terms of the GNU  General Public License as
 *  published by the  Free Software Foundation; either version 2, or
 *  (at your option) any later version.
 *
 *  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
 *  General Public License for more details.
 *
 *  You can view the  GNU General Public License, online, at the GNU
 *  Project's homepage; see <http://www.gnu.org/licenses/gpl.html>.
 */

class KolabMailTransport {
  function KolabMailTransport( $host = '127.0.0.1', $port = 2003 ) {
    $this->host = $host;
    $this->port = $port;
    $this->transport = false;
  }

  /*abstract*/ function createTransport() { 
    myLog("Abstract method KolabMailTransport::createTransport() called!", RM_LOG_ERROR);
  }

  function start($sender,$recips) {
    $this->createTransport();
    $myclass = get_class($this->transport);
    $this->got_newline = true;

    if (!$this->transport) {
      return new PEAR_Error('Failed to connect to $myclass: ' . $error->getMessage(), 421);
    }
    if (PEAR::isError($error = $this->transport->connect())) {
      return new PEAR_Error('Failed to connect to $myclass: ' . $error->getMessage(), 421);
    }

    if (PEAR::isError($error = $this->transport->mailFrom($sender))) {
      $resp = $this->transport->getResponse();
      return new PEAR_Error('Failed to set sender: ' . $resp[1], $resp[0] );
    }
    
    if( !is_array( $recips ) ) $recips = array($recips);

    $reciperrors = array();
    foreach( $recips as $recip ) {
      if (PEAR::isError($error = $this->transport->rcptTo($recip))) {
        $resp = $this->transport->getResponse();
        $msg = "Failed to set recipient $recip: " .$resp[1]. ", code=".$resp[0];
        myLog($msg, RM_LOG_ERROR);
        $reciperrors[] = new PEAR_Error('Failed to set recipient: '.$resp[1], $resp[0]);
      }
    }
    if( count($reciperrors) == count($recips) ) {
      // OK, all failed, just give up
      if( count($reciperrors) == 1 ) {
        // Only one failure, just return that
        return $reciperrors[0];
      }
      // Multiple errors
      return $this->createErrorObject( $reciperrors, 'Delivery to all recipients failed' );
    }

    if (PEAR::isError($error = $this->transport->_put('DATA'))) {      
      return $error;
    }
    if (PEAR::isError($error = $this->transport->_parseResponse(354))) {
      return $error;
    }
    if( !empty($reciperrors) ) {
      return $this->createErrorObject( $reciperrors, 'Delivery to some recipients failed' );
    }
    return true;
  }

  // Encapsulate multiple errors in one
  function createErrorObject( $reciperrors, $msg = null ) {
        // Return the lowest errorcode to not bounce more
        // than we have to
        if($msg == null) $msg = 'Delivery to recipients failed.';
        $code = 1000;
        foreach( $reciperrors as $err ) {
          if( $err->code < $code ) $code = $err->code;
        }
        return new PEAR_Error( $msg, $code, null, null, $reciperrors);  
  }

  /* Modified implementation from Net_SMTP that supports
   * dotstuffing even when getting the mail line-by line */
  function quotedataline(&$data) {
    /*
     * Change Unix (\n) and Mac (\r) linefeeds into Internet-standard CRLF
     * (\r\n) linefeeds.
     */
    //$data = preg_replace("/([^\r]{1})\n/", "\\1\r\n", $data);
    //$data = preg_replace("/\n\n/", "\n\r\n", $data);
    $data = preg_replace(array('/(?<!\r)\n/','/\r(?!\n)/'), "\r\n", $data);
    //$data = preg_replace(array('/(?<!\r)\n/','/\r(?!\n)/'), "\n", $data);

    /*
     * Because a single leading period (.) signifies an end to the data,
     * legitimate leading periods need to be "doubled" (e.g. '..').
     */
    if( $this->got_newline && $data[0] == '.' ) $data = '.'.$data;
    $data = str_replace("\n.", "\n..", $data);
    $len = strlen($data);
    if( $len > 0 )
        $this->got_newline = ( $data[$len-1] == "\n" );
  }

  function data( $data) {
    $this->quotedataline($data);
    if (PEAR::isError($this->transport->_send($data))) {
      return new PEAR_Error('write to socket failed');
    }
    return true;
  }

  function end() {
    if ($this->got_newline) 
        $dot = ".\r\n";
    else
        $dot = "\r\n.\r\n";

    if (PEAR::isError($this->transport->_send($dot))) {
      return new PEAR_Error('write to socket failed');
    }
    if (PEAR::isError($error = $this->transport->_parseResponse(250))) {
      return $error;
    }
    $this->transport->disconnect();
    $this->transport = false;
    return true;
  }

  var $host;
  var $port;
  var $transport;
  var $got_newline;
};

class KolabLMTP extends KolabMailTransport {
  function KolabLMTP( $host = '127.0.0.1', $port = 2003 ) {
    $this->KolabMailTransport($host,$port);
  }

  function createTransport() {
    require_once 'Net/LMTP.php';
    $this->transport = &new Net_LMTP($this->host, $this->port);    
  }
};

class KolabSMTP extends KolabMailTransport {
  function KolabSMTP( $host = '127.0.0.1', $port = 25 ) {
    $this->KolabMailTransport($host,$port);
  }

  function createTransport() {
    require_once 'Net/SMTP.php';
    $this->transport = &new Net_SMTP($this->host, $this->port);    
  }
};

?>

--- NEW FILE: misc.php ---
<?php
/*
 *  Copyright (c) 2004 Klaraelvdalens Datakonsult AB
 *
 *    Writen by Steffen Hansen <steffen at klaralvdalens-datakonsult.se>
 *
 *  This  program is free  software; you can redistribute  it and/or
 *  modify it  under the terms of the GNU  General Public License as
 *  published by the  Free Software Foundation; either version 2, or
 *  (at your option) any later version.
 *
 *  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
 *  General Public License for more details.
 *
 *  You can view the  GNU General Public License, online, at the GNU
 *  Project's homepage; see <http://www.gnu.org/licenses/gpl.html>.
 */

// What myLog levels we can use
define('RM_LOG_SUPER',         -1);
define('RM_LOG_SILENT',         0);
define('RM_LOG_ERROR',          1);
define('RM_LOG_WARN',           2);
define('RM_LOG_INFO',           3);
define('RM_LOG_DEBUG',          4);

$logLevelPrefixes = array(
    RM_LOG_SUPER            => '',
    RM_LOG_SILENT           => '',
    RM_LOG_ERROR            => 'Error',
    RM_LOG_WARN             => 'Warning',
    RM_LOG_INFO             => '',
    RM_LOG_DEBUG            => 'Debug',
);

// What logging mechanisms are available for use
define('RM_LOG_SYSLOG',      1);
define('RM_LOG_FILE',        2);

$logType = 0;
$logPrefix = '';
$logDestination = NULL;

function logInit($name = '')
{
    global $params, $argv, $logType, $logPrefix, $logDestination;

    if (empty($name)) {
        $name = basename($argv[0]);
    }

    if (!array_key_exists('log', $params) || empty($params['log'])) {
        return;
    }

    $matches = array();
    if (preg_match('/(\w+):(.*)?/', $params['log'], $matches)) {
        switch ($matches[1]) {
        case 'syslog':
            $logType = RM_LOG_SYSLOG;
            $txtopts = preg_split('/[\s,]+/', $matches[2], -1, PREG_SPLIT_NO_EMPTY);
            $options = 0;
            foreach ($txtopts as $txtopt) {
                switch ($txtopt) {
                case 'cons': $options |= LOG_CONS; break;
                case 'ndelay': $options |= LOG_NDELAY; break;
                case 'odelay': $options |= LOG_ODELAY; break;
                case 'perror': $options |= LOG_PERROR; break;
                case 'pid': $options |= LOG_PID; break;
                }
            }
            openlog($name, $options, LOG_USER);
            break;

        case 'file':
            $logType = RM_LOG_FILE;
            $logPrefix = $name;
            $logDestination = fopen($matches[2], 'a');
            break;
        }
    }
}

function logClose()
{
    global $logType, $logDestination;

    switch ($logType) {
    case RM_LOG_SYSLOG:
        closelog();
        break;

    case RM_LOG_FILE:
        fclose($logDestination);
        $logDestination = NULL;
        break;
    }
}

function myLog($text, $priority = RM_LOG_INFO)
{
    global $params, $logLevelPrefixes, $logPrefix, $logType, $logDestination;

    if ($params['log_level'] >= $priority) {
        if (!empty($logLevelPrefixes[$priority])) {
            $text = $logLevelPrefixes[$priority] . ": $text";
        }

        switch ($logType) {
        case RM_LOG_SYSLOG:
            syslog(RM_LOG_INFO, $text);
            break;

        case RM_LOG_FILE:
            fwrite($logDestination, strftime('%B %d %T') . " ${logPrefix}[" . getmypid() . "]: $text\n");
            fflush($logDestination);
            break;
        }
    }
}

function init()
{
    global $params;

    define_syslog_variables();

    logInit();

    //myLog('Starting up in ' . ($params['group'] ? 'group' : 'resource') . ' mode', RM_LOG_SUPER);

    $url_fopen = ini_get('allow_url_fopen');
    if (!$url_fopen) {
        myLog('\'allow_url_fopen\' is disabled in php.ini, enabling...', RM_LOG_WARN);
        ini_set('allow_url_fopen', '1');
    }

    // This is used as the default domain for unqualified adresses
    global $_SERVER;
    if (!array_key_exists('SERVER_NAME', $_SERVER)) {
        $_SERVER['SERVER_NAME'] = $params['email_domain'];
    }

    if (!array_key_exists('REMOTE_ADDR', $_SERVER)) {
        $_SERVER['REMOTE_ADDR'] = $params['server'];
    }

    if (!array_key_exists('REMOTE_HOST', $_SERVER)) {
        $_SERVER['REMOTE_HOST'] = $params['server'];
    }
}

/* Since getopt() in php can't parse the options the way
 * postfix gives them to us, we write our own option
 * handling:
 *
 * Inputs:
 *  $opts:  array('a','b','c'), a list of wanted options
 *  $args:  the argv list
 * Output:
 *  array of options and values. For example, the input
 *  "-a foo -b bar baz" would result in 
 *  array( 'a' => 'foo', 'b' => array('bar','baz') )
 */
function parse_args( $opts, $args )
{
  $ret = array();
  for( $i = 0; $i < count($args); ++$i ) {
    $arg = $args[$i];
    if( $arg[0] == '-' ) {
      if( in_array( $arg[1], $opts ) ) {
	$val = array();
	$i++;
	while( $i < count($args) && $args[$i][0] != '-' ) {
	  $val[] = $args[$i];
	  $i++;
	}
	$i--;
	if( array_key_exists($arg[1],$ret) && is_array( $ret[$arg[1]] ) ) $ret[$arg[1]] = array_merge((array)$ret[$arg[1]] ,(array)$val);
	else if( count($val) == 1 ) $ret[$arg[1]] = $val[0];
	else $ret[$arg[1]] = $val;
      }
    }
  }
  return $ret;
}

?>

--- NEW FILE: olhacks.php ---
<?php
/*
 *  Copyright (c) 2004 Klaraelvdalens Datakonsult AB
 *
 *    Writen by Steffen Hansen <steffen at klaralvdalens-datakonsult.se>
 *
 *  This  program is free  software; you can redistribute  it and/or
 *  modify it  under the terms of the GNU  General Public License as
 *  published by the  Free Software Foundation; either version 2, or
 *  (at your option) any later version.
 *
 *  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
 *  General Public License for more details.
 *
 *  You can view the  GNU General Public License, online, at the GNU
 *  Project's homepage; see <http://www.gnu.org/licenses/gpl.html>.
 */

require_once 'Kolab/Filter/misc.php';
require_once HORDE_BASE . '/lib/core.php';
require_once 'Horde/iCalendar.php';
require_once 'Horde/NLS.php';
require_once 'Horde/MIME.php';
require_once 'Horde/MIME/Message.php';
require_once 'Horde/MIME/Headers.php';
require_once 'Horde/MIME/Part.php';
require_once 'Horde/MIME/Structure.php';

$forwardtext = "This is an invitation forwarded by outlook and\n".
  "was rectified by the Kolab server.\n".
  "The invitation was originally sent by\n%s.\n\n".
  "Diese Einladung wurde von Outlook weitergeleitet\n".
  "und vom Kolab-Server in gute Form gebracht.\n".
  "Die Einladung wurde ursprünglich von\n%s geschickt.\n";

/*static*/ function olhacks_mime_parse( &$text ) {
  /* Taken from Horde's MIME/Structure.php */
  require_once 'Mail/mimeDecode.php';

  /* Set up the options for the mimeDecode class. */
  $decode_args = array();
  $decode_args['include_bodies'] = true;
  $decode_args['decode_bodies'] = false;
  $decode_args['decode_headers'] = false;
  
  $mimeDecode = &new Mail_mimeDecode($text, MIME_PART_EOL);
  if (!($structure = $mimeDecode->decode($decode_args))) {
    return false;
  }
  
  /* Put the object into imap_parsestructure() form. */
  MIME_Structure::_convertMimeDecodeData($structure);
  
  return array($structure->headers, $ret = &MIME_Structure::parse($structure));
}

/* static */ function copy_header( $name, &$msg_headers, &$headerarray ) {
  $lname = strtolower($name);
  if( array_key_exists($lname, $headerarray)) {
    if( is_array( $headerarray[$lname] ) ) {
      foreach( $headerarray[$lname] as $h ) {
	$msg_headers->addHeader($name, $h );	
      }
    } else {
      $msg_headers->addHeader($name, $headerarray[$lname] );
    }
  }
}

/*
 * Yet another problem: Outlook seems to remove the organizer
 * from the iCal when forwarding -- we put the original sender
 * back in as organizer.
 */
/* static */ function add_organizer( &$icaltxt, $from ) {
  global $params;
  $iCal = &new Horde_iCalendar();
  $iCal->parsevCalendar($icaltxt);
  $vevent =& $iCal->findComponent('VEVENT');
  if( $vevent ) {
    #myLog("Successfully parsed vevent", RM_LOG_DEBUG);
    if( !$vevent->organizerName() ) {
      #myLog("event has no organizer, adding $from", RM_LOG_DEBUG);
      $adrs = imap_rfc822_parse_adrlist($from, $params['email_domain']);
      if( count($adrs) > 0 ) {
	$org_email = 'mailto:'.$adrs[0]->mailbox.'@'.$adrs[0]->host;
	$org_name  = $adrs[0]->personal;
	if( $org_name ) $vevent->setAttribute( 'ORGANIZER', $org_email, 
					       array( 'CN' => $org_name), false );
	else $vevent->setAttribute( 'ORGANIZER', $org_email, 
				    array(), false );
	myLog("Adding missing organizer '$org_name <$org_email>' to iCal", RM_LOG_DEBUG);
	$icaltxt = $iCal->exportvCalendar();
      }
    }
  }
}

/* Yet another Outlook problem: Some versions of Outlook seems to be incapable
 * of handling non-ascii characters properly in text/calendar parts of
 * a multi-part/mixed mail which we use for forwarding.
 * As a solution, we encode common characters as humanreadable
 * two-letter ascii.
 */
/* static */ function olhacks_recode_to_ascii( $text ) {
  #myLog("recoding \"$text\"", RM_LOG_DEBUG);
  $text = str_replace( ('æ'), 'ae', $text );
  $text = str_replace( ('ø'), 'oe', $text );
  $text = str_replace( ('Ã¥'), 'aa', $text );
  $text = str_replace( ('ä'), 'ae', $text );
  $text = str_replace( ('ö'), 'oe', $text );
  $text = str_replace( ('ü'), 'ue', $text );
  $text = str_replace( ('ß'), 'ss', $text );

  $text = str_replace( ('Æ'), 'Ae', $text );
  $text = str_replace( ('Ø'), 'Oe', $text );
  $text = str_replace( ('Ã…'), 'Aa', $text );
  $text = str_replace( ('Ä'), 'Ae', $text );
  $text = str_replace( ('Ö'), 'Oe', $text );
  $text = str_replace( ('Ü'), 'Ue', $text );
  #myLog("recoded to \"$text\"", RM_LOG_DEBUG);

  return $text;
}

/* main entry point */
function olhacks_embedical( $fqhostname, $sender, $recipients, $origfrom, $subject, $tmpfname ) {
  myLog("Encapsulating iCal message forwarded by $sender", RM_LOG_DEBUG);
  global $forwardtext;
  // Read in message text
  $requestText = '';
  $handle = @fopen( $tmpfname, "r" );
  if( $handle === false ) {
    myLog("Error opening $tmpfname", RM_LOG_ERROR);
    return false;
  }
  while (!feof($handle)) {
    $requestText .= fread($handle, 8192);
  }
  fclose($handle);

  // Parse existing message
  list( $headers, $mime) = olhacks_mime_parse($requestText);
  $parts = $mime->contentTypeMap();
  if( count($parts) != 1 || $parts[1] != 'text/calendar' ) {
    myLog("Message does not contain exactly one toplevel text/calendar part, passing through", 
	  RM_LOG_DEBUG);
    return false;
  }
  $basepart = $mime->getBasePart();

  // Construct new MIME message with original message attached
  $toppart = &new MIME_Message();
  $dorigfrom = Mail_mimeDecode::_decodeHeader($origfrom);
  $textpart = &new MIME_Part('text/plain', sprintf($forwardtext,$dorigfrom,$dorigfrom), 'UTF-8' );
  $ical_txt = $basepart->transferDecode();
  add_organizer($ical_txt, $dorigfrom);
  $msgpart = &new MIME_Part($basepart->getType(), olhacks_recode_to_ascii($ical_txt), 
			    $basepart->getCharset() );
  
  $toppart->addPart($textpart);
  $toppart->addPart($msgpart);
  
  // Build the reply headers.
  $msg_headers = &new MIME_Headers();
  copy_header( 'Received', $msg_headers, $headers );
  //$msg_headers->addReceivedHeader();
  $msg_headers->addMessageIdHeader();
  //myLog("Headers=".print_r($headers,true), RM_LOG_DEBUG);
  copy_header( 'Date', $msg_headers, $headers );
  copy_header( 'Resent-Date', $msg_headers, $headers );
  copy_header( 'Subject', $msg_headers, $headers );
  $msg_headers->addHeader('From', $sender);
  $msg_headers->addHeader('To', join(', ', $recipients));
  $msg_headers->addHeader('X-Kolab-Forwarded', 'TRUE');
  $msg_headers->addMIMEHeaders($toppart);
  copy_header( 'Content-Transfer-Encoding', $msg_headers, $headers );

  if (is_object($msg_headers)) {
    $headerArray = $toppart->encode($msg_headers->toArray(), $toppart->getCharset());
  } else {
    $headerArray = $toppart->encode($msg_headers, $toppart->getCharset());
  }

  // Inject message back into postfix
  require_once 'Mail.php';
  $mailer = &Mail::factory('SMTP', array('auth' => false, 'port' => 10026 ));

  $msg = $toppart->toString();
  /* Make sure the message has a trailing newline. */
  if (substr($msg, -1) != "\n") {
    $msg .= "\n";
  }

  $error = $mailer->send($recipients, $headerArray, $msg);
  if( PEAR::isError($error) ) {
    fwrite(STDOUT, $error->getMessage()."\n"); exit(EX_TEMPFAIL);
  }

  return true;
}

?>

--- NEW FILE: resmgr.php ---
<?php
/*
 *  Copyright (c) 2004 Klaraelvdalens Datakonsult AB
 *
 *    Writen by Steffen Hansen <steffen at klaralvdalens-datakonsult.se>
 *
 *  This  program is free  software; you can redistribute  it and/or
 *  modify it  under the terms of the GNU  General Public License as
 *  published by the  Free Software Foundation; either version 2, or
 *  (at your option) any later version.
 *
 *  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
 *  General Public License for more details.
 *
 *  You can view the  GNU General Public License, online, at the GNU
 *  Project's homepage; see <http://www.gnu.org/licenses/gpl.html>.
 */
[...1437 lines suppressed...]
    unset($imap);
    // Get the resource's free/busy list
    // once more so it is up to date
    if( !triggerFreeBusy($resource,false) ) {
      myLog("Error updating fblist", RM_LOG_SUPER );
    }
    return false;;

  default:
    // We either don't currently handle these iTip methods, or they do not
    // apply to what we're trying to accomplish here
    myLog("Ignoring $method method and passing message through to $resource");
    return true;
  }

  // Pass the message through to the group's mailbox
  myLog("Passing through $method method to $resource");
  return true;
}
?>





More information about the commits mailing list