gunnar: server/php-kolab/Kolab_Filter/Filter Filter.php, NONE, 1.1 Incoming.php, NONE, 1.1 Outgoing.php, NONE, 1.1 Response.php, NONE, 1.1 Transport.php, NONE, 1.1 kolabmailtransport.php, 1.2, NONE misc.php, 1.2, NONE

cvs at kolab.org cvs at kolab.org
Mon Nov 26 17:35:23 CET 2007


Author: gunnar

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

Added Files:
	Filter.php Incoming.php Outgoing.php Response.php 
	Transport.php 
Removed Files:
	kolabmailtransport.php misc.php 
Log Message:
Restructured Kolab_Filter package with the focus on error
handling and unit testing.

--- NEW FILE: Filter.php ---
<?php
/*  
 *  COPYRIGHT
 *  ---------
 *
 *  See ../AUTHORS file
 *
 *
 *  LICENSE
 *  -------
 *
 *  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 of the License, 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 should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  $Revision: 1.1 $
 *
 *  ABOUT
 *  -----
 *
 *  A basic definition for a PHP based postfix filter.
 *
 */

/* Load the required PEAR libraries */
require_once 'PEAR.php';

/* Load the required Horde libraries */
require_once 'Horde.php';

/* Load the Filter libraries */
require_once 'Kolab/Filter/Transport.php';

class Filter 
{
    var $_transport;

    var $_startts;

    var $_tmpdir;
    
    var $_tmpfile;
    var $_tmpfh;

    var $_sender;
    var $_recipients;
    var $_client_address;
    var $_fqhostname;
    var $_sasl_username;

    function Filter($transport = 'StdOut')
    {
        global $conf;

        $this->_transport = $transport;

        $this->_startts = $this->_microtime_float();

        if (!empty($conf['filter']['tempdir'])) {
            $this->_tmpdir = $conf['filter']['tempdir'];
        } else {
            $this->_tmpdir = sys_get_temp_dir();
        }

        /* This is used as the default domain for unqualified adresses */
        global $_SERVER;
        if (!array_key_exists('SERVER_NAME', $_SERVER)) {
            if (!empty($conf['filter']['email_domain'])) {
                $_SERVER['SERVER_NAME'] = $conf['filter']['email_domain'];
            } else {
                $_SERVER['SERVER_NAME'] = 'localhost';
            }
        }
        
        if (!array_key_exists('REMOTE_ADDR', $_SERVER)) {
            if (!empty($conf['filter']['server'])) {
                $_SERVER['REMOTE_ADDR'] = $conf['filter']['server'];
            } else {
                $_SERVER['REMOTE_ADDR'] = 'localhost';
            }
        }
        
        if (!array_key_exists('REMOTE_HOST', $_SERVER)) {
            if (!empty($conf['filter']['server'])) {
                $_SERVER['REMOTE_HOST'] = $conf['filter']['server'];
            } else {
                $_SERVER['REMOTE_HOST'] = 'localhost';
            }
        }
    }

    function _start()
    {
        /* Setup the temporary storage */
        $result = $this->_initTmp();
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }
        
        /* Parse our arguments */
        $result = $this->_parseArgs();
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        Horde::logMessage(sprintf(_("%s starting up (sender=%s, recipients=%s, client_address=%s)"), 
                                  get_class($this), $this->_sender, join(', ',$this->_recipients), $this->_client_address),
                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
    }
    
    function _stop()
    {
        return $this->_microtime_float() - $this->_startts;
    }
    
    function _parseArgs()
    {
        $args = $_SERVER['argv'];
        $opts = array( 's', 'r', 'c', 'h', 'u' );

        $options = 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], $options) &&
                        is_array($options[$arg[1]])) {
                        $options[$arg[1]] = array_merge(
                            (array)$options[$arg[1]],
                            (array)$val
                        );
                    } else if (count($val) == 1) {
                        $options[$arg[1]] = $val[0];
                    } else {
                        $options[$arg[1]] = $val;
                    }
                }
            }
        }

        if (!array_key_exists('r', $options) ||
            !array_key_exists('s', $options)) {
            return PEAR::raiseError(sprintf(_("Usage is %s -s sender at domain -r recipient at domain"),
                                            $args[0]),
                                    OUT_STDOUT || ERR_TEMPFAIL);
        }

        $this->_sender = strtolower($options['s']);

        $recipients = $options['r'];

        /* make sure recipients is an array */
        if (!is_array($recipients)) {
            $recipients = array($recipients);
        }

        /* make recipients lowercase */
        for ($i = 0; $i < count($recipients); $i++) {
            $recipients[$i] = strtolower($recipients[$i]);
        }
        $this->_recipients = $recipients;

        if (!empty($options['c'])) {
            $this->_client_address = $options['c'];
        }
        
        if (!empty($options['h'])) {
            $this->_fqhostname = strtolower($options['h']);
        }
        
        if (!empty($options['u'])) {
            $this->_sasl_username = strtolower($options['u']);
        }
    }
    
    function _initTmp()
    {
        /* Temp file for storing the message */
        $this->_tmpfile = @tempnam($this->_tmpdir, 'IN.' . get_class($this) . '.');
        $this->_tmpfh = @fopen($this->_tmpfile, "w");
        if( !$this->_tmpfh ) {
            $msg = $php_errormsg;
            return PEAR::raiseError(sprintf(_("Error: Could not open %s for writing: %s"),
                                            $this->_tmpfile, $msg),
                                    OUT_LOG || ERR_TEMPFAIL);
        }

        register_shutdown_function(array($this, '_cleanupTmp'));
    }
    
    function _cleanupTmp() {
        if (@file_exists($this->_tmpfile)) {
            @unlink($this->_tmpfile);
        }
    }

    function _microtime_float() 
    {
        list($usec, $sec) = explode(" ", microtime());
        return (float) $usec + (float) $sec;
    }

    function &_getTransport($host, $port)
    {
        $class = 'Transport_' . $this->_transport;
        if (class_exists($class)) {
            $transport = &new $class($host, $port);
            return $transport;
        }
        return PEAR::raiseError(sprintf(_("No such class \"%s\""), $class),
                                OUT_LOG || ERR_TEMPFAIL);
    }

    function _rewriteCode($result) 
    {
        if ($result->getCode() < 500) {
            $code = ERR_TEMPFAIL;
        } else {
            $code = ERR_UNAVAILABLE;
        }
        $append = sprintf(_(", original code %s"), $result->getCode());
        $result->message = $result->getMessage() . $append;
        $result->code = OUT_LOG || OUT_STDOUT || $code;
        return $result;
    }
}

?>
--- NEW FILE: Incoming.php ---
<?php
/*  
 *  COPYRIGHT
 *  ---------
 *
 *  See ../AUTHORS file
 *
 *
 *  LICENSE
 *  -------
 *
 *  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 of the License, 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 should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  $Revision: 1.1 $
 *
 *  ABOUT
 *  -----
 *
 *  A Kolab Server filter for incoming mails that are parsed for iCal
 *  contents.
 *
 */

/* Load the basic filter definition */
require_once 'Kolab/Filter/Filter.php';

class Filter_Incoming extends Filter
{

    var $_add_headers;

    function Filter_Incoming($transport = 'LMTP')
    {
        Filter::Filter($transport);
    }
    
    function parse($inh = STDIN)
    {
        $result = $this->_start();
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        $ical = false;
        $add_headers = array();
        $headers_done = false;

        while (!feof($inh) && !$headers_done) {
            $buffer = fgets($inh, 8192);
            $line = rtrim( $buffer, "\r\n");
            if ($line == '') {
                /* Done with headers */
                $headers_done = true;
            } else if(eregi('^Content-Type: text/calendar', $line)) {
                Horde::logMessage(_("Found iCal data in message"), 
                                  __FILE__, __LINE__, PEAR_LOG_DEBUG);
                $ical = true;
            }
            if (@fwrite($this->_tmpfh, $buffer) === false) {
                $msg = $php_errormsg;
                return PEAR::raiseError(sprintf(_("Error: Could not write to %s: %s"),
                                                $this->_tmpfile, $msg),
                                        OUT_LOG || ERR_TEMPFAIL);
            }
        }

        while (!feof($inh)) {
            $buffer = fread($inh, 8192);
            if (@fwrite($this->_tmpfh, $buffer) === false) {
                $msg = $php_errormsg;
                return PEAR::raiseError(sprintf(_("Error: Could not write to %s: %s"),
                                                $this->_tmpfile, $msg),
                                        OUT_LOG || ERR_TEMPFAIL);
            }
        }

        if (@fclose($this->_tmpfh) === false) {
            $msg = $php_errormsg;
            return PEAR::raiseError(sprintf(_("Error: Failed closing %s: %s"),
                                            $this->_tmpfile, $msg),
                                    OUT_LOG || ERR_TEMPFAIL);
        }

        if ($ical) {
            require_once 'Kolab/Filter/resmgr.php';
            $newrecips = array();
            foreach ($this->_recipients as $recip) {
                Horde::logMessage(sprintf(_("Calling resmgr_filter(%s, %s, %s, %s)"),
                                          $this->_fqhostname, $this->_sender,
                                          $this->_recip, $this->tmpfile), 
                                  PEAR_LOG_DEBUG);
                $rc = resmgr_filter($this->_fqhostname, $this->_sender, $recip,
                                    $this->_tmpfile);

                if (is_a($rc, 'PEAR_Error')) {
                    fwrite(STDOUT, sprintf(_("Filter failed: %s\n"),
                                           $rc->getMessage()));
                    exit(EX_TEMPFAIL);
                } else if ($rc === true) {
                    $newrecips[] = $recip;
                }
            }
            $this->_recipients = $newrecips;
            $this->_add_headers[] = "X-Kolab-Scheduling-Message: TRUE";
        } else {
            $this->_add_headers[] = "X-Kolab-Scheduling-Message: FALSE";
        }

        /* Check if we still have recipients */
        if (empty($this->_recipients)) {
            Horde::logMessage(_("No recipients left."), 
                              __FILE__, __LINE__, PEAR_LOG_DEBUG);
            return;
        } else {
            $result = $this->deliver();
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            }
        }
        
        Horde::logMessage(_("Filter_Incoming successfully completed."), 
                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
    }

    function deliver()
    {
        global $conf;

        if (!empty($conf['filter']['lmtp_host'])) {
            $host = $conf['filter']['lmtp_host'];
        } else {
            $host = 'localhost';
        }
        if (!empty($conf['filter']['lmtp_port'])) {
            $port = $conf['filter']['lmtp_port'];
        } else {
            $port = 2003;
        }

        $transport = $this->_getTransport($host, $port);

        $tmpf = @fopen($this->_tmpfile, 'r');
        if (!$tmpf) {
            $msg = $php_errormsg;
            return PEAR::raiseError(sprintf(_("Error: Could not open %s for writing: %s"),
                                            $this->_tmpfile, $msg),
                                    OUT_LOG || ERR_TEMPFAIL);
        }

        $result = $transport->start($this->_sender, $this->_recipients);
        if (is_a($result, 'PEAR_Error')) {
            return $this->_rewriteCode($result);
        }
        
        $headers_done = false;
        while (!feof($tmpf) && !$headers_done) {
            $buffer = fgets($tmpf, 8192);
            if (!$headers_done && rtrim($buffer, "\r\n") == '') {
                $headers_done = true;
                foreach ($this->_add_headers as $h) {
                    $result = $transport->data("$h\r\n");
                    if (is_a($result, 'PEAR_Error')) {
                        return $this->_rewriteCode($result);
                    }
                }
            }
            $result = $transport->data($buffer);
            if (is_a($result, 'PEAR_Error')) {
                return $this->_rewriteCode($result);
            }
        }

        while (!feof($tmpf)) {
            $buffer = fread($tmpf, 8192);
            $len = strlen($buffer);
            
            /* We can't tolerate that the buffer breaks the data
             * between \r and \n, so we try to avoid that. The limit
             * of 100 reads is to battle abuse
             */
            while ($buffer{$len-1} == "\r" && $len < 8192 + 100) {
                $buffer .= fread($tmpf, 1);
                $len++;
            }
            $result = $transport->data($buffer);
            if (is_a($result, 'PEAR_Error')) {
                return $this->_rewriteCode($result);
            }
        }
        return $transport->end();
    }
}
?>

--- NEW FILE: Outgoing.php ---
<?php
/*  
 *  COPYRIGHT
 *  ---------
 *
 *  See ../AUTHORS file
 *
 *
 *  LICENSE
 *  -------
 *
 *  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 of the License, 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 should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  $Revision: 1.1 $
 *
 *  ABOUT
 *  -----
 *
 *  A Kolab Server filter for outgoing mails.
 *
 */

/* Load the basic filter definition */
require_once 'Kolab/Filter/Filter.php';

define('RM_STATE_READING_HEADER', 1 );
define('RM_STATE_READING_FROM',   2 );
define('RM_STATE_READING_SUBJECT',3 );
define('RM_STATE_READING_SENDER', 4 );
define('RM_STATE_READING_BODY',   5 );

class Filter_Outgoing extends Filter
{
    function Filter_Outgoing($transport = 'SMTP')
    {
        Filter::Filter($transport);
    }
    
    function parse($inh = STDIN)
    {
        global $conf;

        if (!empty($conf['filter']['verify_from_header'])) {
            $verify_from_header = $conf['filter']['verify_from_header'];
        } else {
            $verify_from_header = true;
        }

        if (!empty($conf['filter']['allow_sender_header'])) {
            $allow_sender_header = $conf['filter']['allow_sender_header'];
        } else {
            $allow_sender_header = true;
        }

        if (!empty($conf['filter']['allow_outlook_ical_forward'])) {
            $allow_outlook_ical_forward = $conf['filter']['allow_outlook_ical_forward'];
        } else {
            $allow_outlook_ical_forward = true;
        }

        $result = $this->_start();
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        $ical = false;
        $from = false;
        $subject = false;
        $senderok = true;
        $rewrittenfrom = false;
        $state = RM_STATE_READING_HEADER;

        while (!feof($inh) && $state != RM_STATE_READING_BODY) {

            $buffer = fgets($inh, 8192);
            $line = rtrim($buffer, "\r\n");

            if ($line == '') {
                /* Done with headers */
                $state = RM_STATE_READING_BODY;
                if ($from && $verify_from_header) {
                    $rc = verify_sender($this->_sasl_username, $this->_sender, 
                                        $from, $this->_client_address);
                    if (is_a($rc, 'PEAR_Error')) {
                        return $this->_rewriteCode($rc);
                    } else if ($rc === true) {
                        /* All OK, do nothing */
                    } else if ($rc === false) {
                        /* Reject! */
                        $senderok = false;
                    } else if (is_string($rc)) {
                        /* Rewrite from */
                        if (strpos($from, $rc) === false) {
                            Horde::logMessage(sprintf(_("Rewriting '%s' to '%s'"),
                                                      $from, $to), 
                                              __FILE__, __LINE__, PEAR_LOG_DEBUG);
                            $rewrittenfrom = "From: $rc\r\n";
                        }
                    }
                }
            } else {
                if ($line[0] != ' ' && $line[0] != "\t") {
                    $state = RM_STATE_READING_HEADER;
                }
                switch( $state ) {
                case RM_STATE_READING_HEADER:
                    if ($allow_sender_header &&
                        eregi('^Sender: (.*)', $line, $regs)) {
                        $from = $regs[1];
                        $state = RM_STATE_READING_SENDER;
                    } else if (!$from && eregi('^From: (.*)', $line, $regs)) {
                        $from = $regs[1];
                        $state = RM_STATE_READING_FROM;
                    } else if (eregi('^Subject: (.*)', $line, $regs)) {
                        $subject = $regs[1];
                        $state = RM_STATE_READING_SUBJECT;
                    } else if (eregi('^Content-Type: text/calendar', $line)) {
                        Horde::logMessage(_("Found iCal data in message"),
                                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
                        $ical = true;
                    }
                    break;
                case RM_STATE_READING_FROM:
                    $from .= $line;
                    break;
                case RM_STATE_READING_SENDER:
                    $from .= $line;
                    break;
                case RM_STATE_READING_SUBJECT:
                    $subject .= $line;
                    break;
                }
            }
            if (@fwrite($this->_tmpfh, $buffer) === false) {
                $msg = $php_errormsg;
                return PEAR::raiseError(sprintf(_("Error: Could not write to %s: %s"),
                                                $this->_tmpfile, $msg),
                                        OUT_LOG || ERR_TEMPFAIL);
            }
        }
        while (!feof($inh)) {
            $buffer = fread($inh, 8192);
            if (@fwrite($this->_tmpfh, $buffer) === false) {
                $msg = $php_errormsg;
                return PEAR::raiseError(sprintf(_("Error: Could not write to %s: %s"),
                                                $this->_tmpfile, $msg),
                                        OUT_LOG || ERR_TEMPFAIL);
            }
        }

        if (@fclose($this->_tmpfh) === false) {
            $msg = $php_errormsg;
            return PEAR::raiseError(sprintf(_("Error: Failed closing %s: %s"),
                                            $this->_tmpfile, $msg),
                                    OUT_LOG || ERR_TEMPFAIL);
        }

        if (!$senderok) {
            if ($ical && $allow_outlook_ical_forward ) {
                require_once('Kolab/Filter/olhacks.php');
                $rc = olhacks_embedical($this->_fqhostname, $this->_sender, $this->_recipients, 
                                        $from, $subject, $this->_tmpfname);
                if (is_a($rc, 'PEAR_Error')) {
                    return $this->_rewriteCode($rc);
                } else if ($rc === true) {
                    return;
                }
            } else {
                return PEAR::raiseError(sprintf(_("Invalid From: header. %s looks like a forged sender"),
                                                $from),
                                        OUT_LOG || OUT_STDOUT || ERR_UNAVAILABLE);
            }
        }

        $result = $this->deliver($rewrittenfrom);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }
        
        Horde::logMessage(_("Filter_Outgoing successfully completed."), 
                          __FILE__, __LINE__, PEAR_LOG_DEBUG);
    }

    function deliver($rewrittenfrom)
    {
        global $conf;

        if (!empty($conf['filter']['smtp_host'])) {
            $host = $conf['filter']['smtp_host'];
        } else {
            $host = 'localhost';
        }
        if (!empty($conf['filter']['smtp_port'])) {
            $port = $conf['filter']['smtp_port'];
        } else {
            $port = 10025;
        }

        $transport = $this->_getTransport($host, $port);

        $tmpf = @fopen($this->_tmpfile, 'r');
        if (!$tmpf) {
            $msg = $php_errormsg;
            return PEAR::raiseError(sprintf(_("Error: Could not open %s for writing: %s"),
                                            $this->_tmpfile, $msg),
                                    OUT_LOG || ERR_TEMPFAIL);
        }

        $result = $transport->start($this->_sender, $this->_recipients);
        if (is_a($result, 'PEAR_Error')) {
            return $this->_rewriteCode($result);
        }

        $state = RM_STATE_READING_HEADER;
        while (!feof($tmpf) && $state != RM_STATE_READING_BODY) {
            $buffer = fgets($tmpf, 8192);
            if ($rewrittenfrom) {
                if (eregi( '^From: (.*)', $buffer)) {
                    $result = $transport->data($rewrittenfrom);
                    if (is_a($result, 'PEAR_Error')) {
                        return $this->_rewriteCode($result);
                    }
                    $state = RM_STATE_READING_FROM;
                    continue;
                } else if ($state == RM_STATE_READING_FROM &&
                           ($buffer[0] == ' ' || $buffer[0] == "\t")) {
                    /* Folded From header, ignore */
                    continue;
                }
            }
            if (rtrim($buffer, "\r\n") == '') {
                $state = RM_STATE_READING_BODY;
            } else if ($buffer[0] != ' ' && $buffer[0] != "\t")  {
                $state = RM_STATE_READING_HEADER;
            }
            $result = $transport->data($buffer);
            if (is_a($result, 'PEAR_Error')) {
                return $this->_rewriteCode($result);
            }
        }
        while (!feof($tmpf)) {
            $buffer = fread($tmpf, 8192);
            $len = strlen($buffer);

            /* We can't tolerate that the buffer breaks the data
             * between \r and \n, so we try to avoid that. The limit
             * of 100 reads is to battle abuse
             */
            while ($buffer{$len-1} == "\r" && $len < 8192 + 100) {
                $buffer .= fread($tmpf,1);
                $len++;
            }
            $result = $transport->data($buffer);
            if (is_a($result, 'PEAR_Error')) {
                return $this->_rewriteCode($result);
            }
        }
        return $transport->end();
    }
}

// Cleanup function
function is_my_domain( $addr ) {
  global $params;
  if( is_array($params['email_domain']) ) {
	$domains = $params['email_domain'];
  } else {
	$domains = array($params['email_domain']);
  }
  
  $adrs = imap_rfc822_parse_adrlist($addr, $params['email_domain']);
  foreach ($adrs as $adr) {
    $adrdom = $adr->host;
    if( empty($adrdom) ) continue;
    foreach( $domains as $dom ) {
      if( $dom == $adrdom ) return true;
      if( $params['verify_subdomains'] && substr($adrdom, -strlen($dom)-1) == ".$dom" ) return true;
    }
  }
  return false;
}

/**
 Returns a list of allowed email addresses for user $sasluser
 or a PEAR_Error object if something croaked.
*/
function addrs_for_uid( $sasluser )
{
  global $params;
  /* Connect to the LDAP server and retrieve the users'
   allowed email addresses */
  $ldap = ldap_connect($params['ldap_uri']);
  if (!ldap_bind($ldap, $params['bind_dn'], $params['bind_pw'])) {
    myLog('Unable to contact LDAP server: ' . ldap_error($ldap));
    return new PEAR_Error('Unable to contact LDAP server: ' . ldap_error($ldap));
  }
  
  $filter = "(&(objectClass=kolabInetOrgPerson)(|(mail=$sasluser)(uid=$sasluser)))";
  $result = ldap_search($ldap, $params['base_dn'],
			$filter,
			array("dn", "mail", "alias" ));
  if (!$result) {
    myLog('Unable to perform LDAP search: ' . ldap_error($ldap));
    return new PEAR_Error('Unable to perform LDAP search: ' . ldap_error($ldap));
  }
  
  $entries = ldap_get_entries($ldap, $result);
  if ($entries['count'] != 1) {
    myLog($entries['count']." objects returned for uid $sasluser");
    return new PEAR_Error("Temporary LDAP error, unable to look up user $sasluser");
  }
  unset($entries[0]['mail']['count']);
  unset($entries[0]['alias']['count']);
  $addrs = array_merge((array) $entries[0]['mail'],(array) $entries[0]['alias']);
  $mail = $entries[0]['mail'][0];

  ldap_free_result($result);

  $filter = "(&(objectClass=kolabInetOrgPerson)(kolabDelegate=$mail))";
  $result = ldap_search($ldap, $params['base_dn'],
			$filter,
			array("dn", "mail" ));
  if (!$result) {
    myLog('Unable to perform LDAP search: ' . ldap_error($ldap));
    return new PEAR_Error('Unable to perform LDAP search: ' . ldap_error($ldap));
  }
  
  $entries = ldap_get_entries($ldap, $result);
  unset( $entries['count'] );
  foreach( $entries as $adr ) {
    if( $adr['mail']['count'] > 0 ) {
      unset($adr['mail']['count']);
      $addrs = array_merge((array) $addrs,(array) $adr['mail']);
    }
  }
  ldap_free_result($result);
  ldap_close($ldap);

  #myLog("Found addresses ".print_r($addrs,true)." for user $sasluser", RM_LOG_DEBUG);
  return $addrs;
}

/** Returns the format string used to rewrite
    the From header for untrusted messages */
function get_untrusted_subject_insert($sasluser,$sender)
{
  global $params;
  if( $sasluser ) {
    if( array_key_exists('untrusted_subject_insert', $params) ) {
      $fmt = $params['untrusted_subject_insert'];
    } else {
      $fmt = "(UNTRUSTED, sender is <%s>)";
    }
  } else {
    if( array_key_exists('unauthenticated_subject_insert', $params) ) {
      $fmt = $params['unauthenticated_subject_insert'];
    } else {
      $fmt = "(UNTRUSTED, sender <%s> is not authenticated)";
    }
  }
  return sprintf($fmt,$sender);
}

/** Check that the From header is not trying
    to impersonate a valid user that is not
    $sasluser. Returns one of:

    * True if From can be accepted
    * False if From must be rejected
    * A string with a corrected From header that makes
      From acceptable
    * A PEAR_Error object if something croaked
*/
function verify_sender( $sasluser, $sender, $fromhdr, $client_addr ) {
  global $params;

  /* Allow anything from localhost and
     fellow Kolab-hosts */
  if( $client_addr == $params['local_addr'] ) return true;
  $kolabhosts = split(',', $params['kolabhosts'] );
  $kolabhosts = array_map( "gethostbyname", $kolabhosts );
  if( array_search( $client_addr, $kolabhosts ) !== false ) return true;

  if( is_array($params['email_domain']) ) {
    $domains = $params['email_domain'];
  } else {
    $domains = array($params['email_domain']);
  }

  if( $sasluser ) {
    if( PEAR::isError($allowed_addrs = addrs_for_uid($sasluser)) ) {
      myLog("Error reading allowed addresses for $sasluser: ".$allowed_addrs->getMessage(), RM_LOG_ERROR);
      return $allowed_addrs;
    }
  } else {
    $allowed_addrs = false;
  }
  $untrusted = get_untrusted_subject_insert($sasluser,$sender);
  $adrs = imap_rfc822_parse_adrlist($fromhdr, $params['email_domain'][0]);
  foreach ($adrs as $adr) {
    $from = $adr->mailbox.'@'.$adr->host;
    $fromdom = $adr->host;
    if( $sasluser ) {
      if( !in_array( strtolower($from), $allowed_addrs ) ) {
	myLog("$from is not an allowed From address for $sasluser", RM_LOG_DEBUG);
	return false;
      }
    } else {
      foreach( $domains as $domain ) {
	if( strtolower($fromdom) == $domain 
	    || ( $params['verify_subdomains'] 
		 && substr($fromdom, -strlen($domain)-1) == ".$domain" ) ) {
	  if( $params['reject_forged_from_header'] ) {
	    myLog("$from is not an allowed From address for unauthenticated users", RM_LOG_DEBUG);	    
	    return false;
	  } else {
	    /* Rewrite */
	    myLog("$from is not an allowed From address for unauthenticated users, rewriting", RM_LOG_DEBUG);
	    
	    if( strpos( $fromhdr, $untrusted )===false ) {
	      return '"'.str_replace(array("\\",'"'),array("\\\\",'\"'),$adr->personal).' '.$untrusted.'" '.'<'.$from.'>';
	    } else {
	      return true;
	    }
	  }
	}
      }
    }
  }

  /* All seems OK */
  return true;


  /* TODO: What do we do about subdomains? */
  /*
    $senderdom = substr(strrchr($sender, '@'), 1);
    foreach( $domains as $domain ) {
      if( $params['verify_subdomains'] ) {	
	if( ($senderdom == $domain ||
	     $fromdom   == $domain ||
	     substr($senderdom, -strlen($domain)-1) == ".$domain" ||
	     substr($fromdom, -strlen($domain)-1) == ".$domain" ) &&
	    $sender != $from ) {
	  return false;
	}
      } else {
	if( ($senderdom == $domain ||
	     $fromdom   == $domain ) &&
	    $sender != $from ) {
		  return false;
	}
      }
    }
  }
  return true;
  */
}


?>
--- NEW FILE: Response.php ---
<?php
/*  
 *  COPYRIGHT
 *  ---------
 *
 *  See ../AUTHORS file
 *
 *
 *  LICENSE
 *  -------
 *
 *  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 of the License, 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 should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  $Revision: 1.1 $
 *
 *  ABOUT
 *  -----
 *
 *  Provides error handling for the Kolab server filter scripts.
 *
 */

/* Some output constants */
define( 'OUT_STDOUT', 1 );
define( 'OUT_LOG', 2 );

/* Some error constants */
define( 'ERR_TEMPFAIL', 8 );
define( 'ERR_UNAVAILABLE', 16 );

/* Some failure constants */
define( 'EX_TEMPFAIL', 75 );
define( 'EX_UNAVAILABLE', 69 );

class Response
{
    function Response($error_log = '', $debug = false)
    {
        /* Always display all possible problems */
        ini_set('error_reporting', E_ALL);
        ini_set('track_errors', '1');

        /* Setup error logging */
        if (!empty($error_log)) {
            ini_set('log_errors', '1');
            ini_set('error_log', $error_log);
        }

        /* Print PHP messages to StdOut if we are debugging */
        if ($debug) {
            ini_set('display_errors', '1');
        }
    }

    function handle($result)
    {
        /* No error? Be happy and exit clean */
        if (!is_a($result, 'PEAR_Error')) {
            exit(0);
        }

        $msg = $result->getMessage();
        $code = $result->getCode();

        if ($code && OUT_STDOUT) {
            fwrite(STDOUT, $msg);
        }
        if  ($code && OUT_LOG) {
            $frame = $result->getBacktrace(2);
            Horde::logMessage($msg, $frame['file'], $frame['line']);
        }

        // FIXME: Add a userinfo handler in case there were multiple
        // combined errors

        if ($code && ERR_TEMPFAIL) {
            exit(EX_TEMPFAIL);
        } else if ($code && ERR_UNAVAILABLE) {
            exit(EX_UNAVAILABLE);
        } else {
            exit(0);
        }
    }
}

?>
--- NEW FILE: Transport.php ---
<?php
/*  
 *  COPYRIGHT
 *  ---------
 *
 *  See ../AUTHORS file
 *
 *
 *  LICENSE
 *  -------
 *
 *  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 of the License, 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 should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  $Revision: 1.1 $
 *
 *  ABOUT
 *  -----
 *
 *  FIXME
 *
 */

class Transport 
{
    var $host;
    var $port;
    var $transport;
    var $got_newline;

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

    function &createTransport() { 
        return PEAR::raiseError(_("Abstract method Transport::createTransport() called!"));
    }

    function start($sender, $recips)
    {
        $transport = $this->createTransport();
        if (is_a($transport, 'PEAR_Error')) {
            return $transport;
        }
        $this->transport = $transport;
        
        $myclass = get_class($this->transport);
        $this->got_newline = true;

        $result = $this->transport->connect();
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }
        
        $result = $this->transport->mailFrom($sender);
        if (is_a($result, 'PEAR_Error')) {
            $resp = $this->transport->getResponse();
            return PEAR::raiseError(sprintf(_("Failed to set sender: %s, code=%s"),
                                            $resp[1], $resp[0]), $resp[0]);
        }
    
        if (!is_array($recips)) {
            $recips = array($recips);
        }

        $reciperrors = array();
        foreach ($recips as $recip) {
            $result = $this->transport->rcptTo($recip);
            if (is_a($result, 'PEAR_Error')) {
                $resp = $this->transport->getResponse();
                $reciperrors[] = PEAR::raiseError(sprintf(_("Failed to set recipient: %s, code=%s"),
                                                          $resp[1], $resp[0]), $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!"));
        }

        $result = $this->transport->_put('DATA');
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        $result = $this->transport->_parseResponse(354);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }
        
        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(array('/(?<!\r)\n/','/\r(?!\n)/'), "\r\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);
        $result = $this->transport->_send($data);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }
        return true;
    }

    function end() 
    {
        if ($this->got_newline) {          
            $dot = ".\r\n";
        } else {
            $dot = "\r\n.\r\n";
        }
        
        $result = $this->transport->_send($dot);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }
        $result = $this->transport->_parseResponse(250);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }
        $this->transport->disconnect();
        $this->transport = false;
        return true;
    }
}

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

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

}

class Transport_SMTP extends Transport
{

    function Transport_SMTP($host = '127.0.0.1', $port = 25)
    {
        $this->Transport($host,$port);
    }

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

class StdOutWrapper
{
    function connect()
    {
        return true;
    }

    function disconnect()
    {
        return true;
    }

    function mailFrom($sender)
    {
        return fwrite(STDOUT, sprintf(_("Mail from sender: %s\n"), $sender));
    }

    function rcptTo($recipient)
    {
        return fwrite(STDOUT, sprintf(_("Mail to recipient: %s\n"), $recipient));
    }

    function _put($cmd)
    {
        return true;
    }

    function _parseResponse($code)
    {
        return true;
    }

    function _send($data)
    {
        return fwrite(STDOUT, $data);
    }
}

class Transport_StdOut extends Transport
{

    function Transport_SMTP($host = 'irrelevant', $port = 0)
    {
    }

    function &createTransport()
    {
        $transport = &new StdOutWrapper();
        return $transport;
    }
}

?>

--- kolabmailtransport.php DELETED ---

--- misc.php DELETED ---





More information about the commits mailing list