gunnar: server/php-kolab/Kolab_Filter/Filter Content.php, NONE, 1.1 Outgoing.php, 1.5, NONE

cvs at kolab.org cvs at kolab.org
Tue Nov 27 16:31:46 CET 2007


Author: gunnar

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

Added Files:
	Content.php 
Removed Files:
	Outgoing.php 
Log Message:
Rename Outgoing to Content and make the tests work again.

--- NEW FILE: Content.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_Content extends Filter
{
    function Filter_Content($transport = 'SMTP', $debug = false)
    {
        Filter::Filter($transport, $debug);
    }
    
    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;
        }

        $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 $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;
                    } else if (eregi('^Message-ID: (.*)', $line, $regs)) {
                        $this->_id = $regs[1];
                    }
                    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 | EX_IOERR);
            }
        }
        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 | EX_IOERR);
            }
        }

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

        if (!$senderok) {
            if ($ical && $allow_outlook_ical_forward ) {
                require_once('Kolab/Filter/Outlook.php');
                $rc = olhacks_embedical($this->_fqhostname, $this->_sender, $this->_recipients, 
                                        $from, $subject, $this->_tmpfname);
                if (is_a($rc, 'PEAR_Error')) {
                    return $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 | EX_NOPERM);
            }
        }

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

    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 | EX_IOERR);
        }

        $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 $conf;

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

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

    $domains = (array) $email_domain;
  
    $adrs = imap_rfc822_parse_adrlist($addr, $email_domain);
    foreach ($adrs as $adr) {
        $adrdom = $adr->host;
        if (empty($adrdom)) {
            continue;
        }
        foreach ($domains as $dom) {
            if ($dom == $adrdom) {
                return true;
            }
            if ($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 $conf;

    /* Connect to the LDAP server and retrieve the users'
     * allowed email addresses 
     */
    $ldap = ldap_connect($conf['filter']['ldap_uri']);

    if (!ldap_bind($ldap, $conf['filter']['bind_dn'], $conf['filter']['bind_pw'])) {
        return PEAR::raiseError(sprintf(_("Unable to contact LDAP server: %s"),
                                        ldap_error($ldap)),
                                OUT_LOG | EX_TEMPFAIL);
    }
  
    $filter = "(&(objectClass=kolabInetOrgPerson)(|(mail=$sasluser)(uid=$sasluser)))";
    $result = ldap_search($ldap, $conf['filter']['base_dn'],
                          $filter,
                          array("dn", "mail", "alias" ));
    if (!$result) {
        return PEAR::raiseError(sprintf(_("Unable to perform LDAP search: %s"),
                                        ldap_error($ldap)),
                                OUT_LOG | EX_TEMPFAIL);
    }
  
    $entries = ldap_get_entries($ldap, $result);
    if ($entries['count'] != 1) {
        return PEAR::raiseError(sprintf(_("%s objects returned for uid %s. Unable to look up user."),
                                        $entries['count'], $sasluser),
                                OUT_LOG | EX_TEMPFAIL);
    }
    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, $conf['filter']['base_dn'],
                          $filter,
                          array("dn", "mail" ));
    if (!$result) {
        return PEAR::raiseError(sprintf(_("Unable to perform LDAP search: %s"),
                                        ldap_error($ldap)),
                                OUT_LOG | EX_TEMPFAIL);
    }
  
    $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);

    return $addrs;
}

/** Returns the format string used to rewrite
    the From header for untrusted messages */
function get_untrusted_subject_insert($sasluser,$sender)
{
    global $conf;

    if ($sasluser) {
        if (!empty($conf['filter']['untrusted_subject_insert'])) {
            $fmt = $conf['filter']['untrusted_subject_insert'];
        } else {
            $fmt = _("(UNTRUSTED, sender is <%s>)");
        }
    } else {
        if (!empty($conf['filter']['unauthenticated_subject_insert'])) {
            $fmt = $conf['filter']['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 $conf;

    if (!empty($conf['filter']['email_domain'])) {
        $domains = $conf['filter']['email_domain'];
    } else {
        $domains = 'localhost';
    }

    if (!is_array($domains)) {
        $domains = array($domains);
    }
  
    if (!empty($conf['filter']['local_addr'])) {
        $local_addr = $conf['filter']['local_addr'];
    } else {
        $local_addr = 'localhost';
    }

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

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

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

    /* Allow anything from localhost and
     * fellow Kolab-hosts 
     */
    if ($client_addr == $local_addr) {
        return true;
    }
    
    $kolabhosts = split(',', $kolabhosts);
    $kolabhosts = array_map('gethostbyname', $kolabhosts );

    if (array_search($client_addr, $kolabhosts) !== false) {
        return true;
    }
    
    if ($sasluser) {
        $allowed_addrs = addrs_for_uid($sasluser);
        if (is_a($allowed_addrs, 'PEAR_Error')) {
            return $allowed_addrs;
        }
    } else {
        $allowed_addrs = false;
    }

    $untrusted = get_untrusted_subject_insert($sasluser,$sender);
    $adrs = imap_rfc822_parse_adrlist($fromhdr, $domains[0]);

    foreach ($adrs as $adr) {
        $from = $adr->mailbox . '@' . $adr->host;
        $fromdom = $adr->host;
        if ($sasluser) {
            if (!in_array(strtolower($from), $allowed_addrs)) {
                Horde::logMessage(sprintf(_("%s is not an allowed From address for %s"), 
                                          $from, $sasluser), __FILE__, __LINE__, PEAR_LOG_DEBUG);
                return false;
            }
        } else {
            foreach ($domains as $domain) {
                if (strtolower($fromdom) == $domain 
                    || ($verify_subdomains
                        && substr($fromdom, -strlen($domain)-1) == ".$domain")) {
                    if ($reject_forged_from_header) {
                        Horde::logMessage(sprintf(_("%s is not an allowed From address for unauthenticated users."), 
                                                  $from), __FILE__, __LINE__, PEAR_LOG_DEBUG);
                        return false;
                    } else {
                        /* Rewrite */
                        Horde::logMessage(sprintf(_("%s is not an allowed From address for unauthenticated users, rewriting."), 
                                                  $from), __FILE__, __LINE__, PEAR_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( $conf['filter']['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;
    */

}


?>
--- Outgoing.php DELETED ---





More information about the commits mailing list