1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/z-push_ynh.git synced 2024-09-03 18:05:58 +02:00

use Z-Push-contrib

This commit is contained in:
Adrien Beudin 2014-12-18 18:08:26 +01:00
parent bd0487c071
commit 918ef6c246
170 changed files with 26073 additions and 1003 deletions

112
sources/README.md Normal file
View file

@ -0,0 +1,112 @@
Z-Push-contrib
==============
This is a Z-Push fork with changes that I will try to put into the contrib branch, so they can get into the official Z-Push
IMPORTANT:
For them to get into the official Z-Push, you must release the code under AGPLv3. Add this text to your commits "Released under the Affero GNU General Public License (AGPL) version 3" before merging.
If you see some changes here, and you are the author, I will not contrib them, as I have no rights over them.
But I will try to reimplement with different code/approach so I can contribute them. When a sustitution is ready I will remove your changes from this repo.
If you want to help the community, contribute them yourself.
IMPORTANT 2:
All the code is AGPL licensed (or compatible, like the "include" files). So you can get a copy, modify it, use... your only obligation it's to publish your changes somewhere.
----------------------------------------------------
Original Z-Push
URL: http://www.zpush.org
Z-Push is an implementation of the ActiveSync protocol, which is used 'over-the-air' for multi platform ActiveSync devices, including Windows Mobile, Ericsson and Nokia phones. With Z-Push any groupware can be connected and synced with these devices.
License: GNU Affero Genaral Public License v3.0 (AGPLv3)
Documentation
=============
You can find some configuration guidelines in the Wiki https://github.com/fmbiete/Z-Push-contrib/wiki
Requisites
==========
- PHP 5.5 (5.3 should also work, 5.4 it's fine, but 5.5 is better)
- NGINX or APACHE
- PHP-FPM or MOD_PHP
Configuration
=============
NGINX, 1.4 at least or you will need to enable chunkin mode (Use google for Apache configuration)
server {
listen 443;
server_name zpush.domain.com;
ssl on;
ssl_certificate /etc/ssl/certs/zpush.pem;
ssl_certificate_key /etc/ssl/private/zpush.key;
root /usr/share/www/z-push-contrib;
index index.php;
error_log /var/log/nginx/zpush-error.log;
access_log /var/log/nginx/zpush-access.log;
location / {
try_files $uri $uri/ index.php;
}
location /Microsoft-Server-ActiveSync {
rewrite ^(.*)$ /index.php last;
}
location ~ .php$ {
include /etc/nginx/fastcgi_params;
fastcgi_index index.php;
fastcgi_param HTTPS on;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/var/run/php5-fpm.sock;
# Z-Push Ping command will be alive for 470s, but be safe
fastcgi_read_timeout 630;
}
}
PHP-FPM
max_execution_time=600
short_open_tag=On
And configure enough php-fpm processes, as a rough estimation you will need 1.5 x number users.
Backends
========
Each backend has a README file, or comments in their config.php file. Look at them to configure correctly. Also you can look here https://github.com/fmbiete/Z-Push-contrib/wiki
StateMachine
============
You have 2 StateMachine methods.
- FILE - FileStateMachine : will store state info into files. You will use it in Active-Pasive setups
- SQL - SqlStateMachine: will store state info into a database. It uses PHP-PDO, so you will need to install the required packages for your database flavour and create the database. You will use it in Active-Active setups.
User-Device Permissions
=======================
Disabled by default, when enabled will limit what users and device can sync against your Z-Push installation.
It can auto-accept users, users and device until a maximum number of devices is reached.
If using with FileStateMachine, edit the file STATE_DIR/PreAuthUserDevices to modificate the behaivour. That file is JSON formatted and it's filled each time a new user connect.
If using with SqlStateMachine, look at the zpush_preauth_users table.
Links
=====
Microsoft ActiveSync Specification
http://msdn.microsoft.com/en-us/library/cc425499%28v=exchg.80%29.aspx

View file

@ -0,0 +1,208 @@
Z-Push AutoDiscover manual
--------------------------
This manual gives an introduction to the Z-Push AutoDiscover service, discusses
technical details and explains the installation.
Introduction
------------
AutoDiscover is the service used to simplify the configuration of collaboration
accounts for clients, especially for mobile phones.
While in the past the user was required to enter the server name, user name and
password manually into his mobile phone in order to connect, with AutoDiscover
the user is only required to fill in his email address and the password.
AutoDiscover will try several methods to reach the correct server automatically.
How does it work?
-----------------
When speaking about AutoDiscover, this includes two distinct realms:
- AutoDiscover is a specification which defines the steps a client should take
in order to contact a service to request additional data.
- The AutoDiscover service is piece of software which accepts requests from the
clients, authenticates them, requests some additional data from the
collaboration server and sends this data back to the client.
The specification suggests several ways for client to contact the responsible
server to receive additional information. Tests have shown, that basically all
mobile phones tested support only the most basic ways. These are sufficient for
almost all types of scenarios and are the ones implemented by Z-Push AutoDiscover.
Please refer to the Mobile Compatibility List (http://z-push.sf.net/compatibility)
for an overview of supported and tested devices.
The used email address is the key for the process. The client splits it up into
the local and domain part (before and after the @-sign). The client then tries
to connect to this domain in order to get in contact with the AutoDiscover
service. The local part of the email address is used as "login" to the
AutoDiscover service. There is also an option, to use the full email address as
login name (see "Configuration" section below for details).
---------------
| Client |
| e.g. mobile |
---------------
/ \
1. Searches for / \ 2. Data access
information / \
/ \
V V
---------------- --------------
| AutoDiscover | redirects to | Z-Push |
| | --------------------> | ActiveSync |
---------------- --------------
\ /
Authen- \ / Synchronizes
ticates \ /
via Z-Push \ /
Backend V V
-----------------
| Collaboration |
| Platform |
-----------------
Requirements
------------
As described in the previous chapter, the local part of the email address or
the email address is used in order to log in.
Your configuration requires that this type of login is possible:
- either the user name is used to login and must be used in the email address
entered on the mobile, or
- the entire email address is used to login.
Which option is used has to be configured in the AutoDiscover configuration and
in the underlying platform (e.g. ZCP (hosting mode)).
Most companies use the user name as local part of the email by default. From the
AutoDiscover point of view, it is not required that user is able to receive
emails at the used email address. It is recommended allowing that in order not
to confuse end users.
AutoDiscover also requires a valid SSL certificate to work as expected. A very
little percentage of mobiles support self-signed certificates (showing a
pop-up alerting the user). Most mobiles silently ignore self-signed certificates
and just declare the AutoDiscover process as failed in such cases.
If AutoDiscover fails, the user is generally redirected to the
"manual configuration" of the client.
If you do not plan to acquire an official certificate, you will probably not be
able to use the AutoDiscover service.
Depending on your setup, it could be necessary to add new DNS entries for your
mail domain.
Domain setup
------------
There are two general ways the AutoDiscover process can be configured:
1. Directly with "yourdomain.com" website ("www.yourdomain.com" will most
probably not work)
2. With the sub-domain "autodiscover.yourdomain.com"
In both cases, an official SSL certificate is required. If you already have a
certificate for your domain, the webserver answering for that domain could be
reconfigured to allow AutoDiscover requests as well. In the case that you do
not have direct access to this type of configuration (e.g. hosting provider),
it's recommended to acquire a dedicated certificate for
"autodiscover.yourdomain.com". Please note, that this sub-domain can NOT be
renamed. In general, "wildcard" certificates can be used, as long they are
valid for the required domain.
Software requirements
---------------------
Like Z-Push, AutoDiscover is written in PHP, where PHP 5.1 or newer is required.
Please consult the Z-Push INSTALL file for further information about PHP versions.
If only AutoDiscover is to be executed on a host, the Z-Push PHP dependencies do
NOT need to be installed.
AutoDiscover has one direct dependency, the php-xml parser library.
These packages vary in names between the distributions.
- Generally install the packages: php-xml
- On Suse (SLES & OpenSuse) install the packages: php53-xml
- On RHEL based systems install the package: php-xml
Installation
------------
AutoDiscover is part of the Z-Push package and uses some of the functionality
available in Z-Push.
It is possible to install AutoDiscover on the same host as Z-Push, or to
install them on different hosts.
Currently, independently from the setup, it's recommended to extract the entire
z-push tarball and configure the services as required.
Please follow the install instructions from the Z-Push INSTALL file (section
"How to install") to copy the files to your server.
If you do not want to setup Z-Push on the host, do not add the "Alias" for
ActiveSync.
To setup the SSL certificate, please refer to one of the many setup guides
available on the internet, like that one:
http://www.apache.com/resources/how-to-setup-an-ssl-certificate-on-apache/
The mobiles requests these URLs (where "yourdomain.com" corresponds to the
domain part of the email used in the client):
https://yourdomain.com/Autodiscover/Autodiscover.xml and/or
https://autodiscover.yourdomain.com/Autodiscover/Autodiscover.xml
Add the following line to the apache site configuration file.
AliasMatch (?i)/Autodiscover/Autodiscover.xml "/usr/share/z-push/autodiscover/autodiscover.php"
This line assumes that Z-Push is installed in /usr/share/z-push. If the path
is different, please adjust it accordingly.
Note: some mobiles use different casings, like "AutoDiscover" in the URL. The
above statement is valid for these as well.
Please restart Apache afterwards.
Configuration
-------------
There are several parameters in the configuration file, which allow to customize
the behaviour of the AutoDiscover Service.
The configuration, generally is located in the z-push/autodiscover directory and
is called "config.php".
The parameters:
BASE_PATH This property specifies where the AutoDiscover files are
located. Normally there is no need to adjust this parameter.
SERVERURL This is the full URL where the Z-Push server is available.
You should adjust it to the domain/server where Z-Push is
installed.
USE_FULLEMAIL_FOR_LOGIN If this is set to "true", AutoDiscover will attempt to
login on the collaboration server with the full email
address sent by the client. If disabled (default), the
local part of the email address is used.
LOGFILEDIR The directory where logfiles are created.
LOGFILE The default AutoDiscover log file.
LOGERRORFILE The default AutoDiscover error log file.
LOGLEVEL The loglevel, set it to WBXML to see the data received
and sent from/to clients.
LOGAUTHFAIL Set to true, to explicitly log failed login attempts.
BACKEND_PROVIDER The backend to be used. If empty (default) the code
will auto detect which backend to use.
Please note: the desired backend also needs to be configured, in the
"backends/<backend>/config.php" file.
Test installation
-----------------
If everything is correct, accessing with a browser the URL for your setup, you
should see:
1. a pop-up asking for your username + password. Always use the email
address which you would also enter on the mobile (independently from
the configuration).
2. if the authentication was successful, you will see a Z-Push informational
page (like when accessing the Z-Push location).
Note: The same test can also be performed in the mobiles web browser to check
if the access works correctly from the mobile network.
If the authentication fails, please check the configuration options of AutoDiscover.
Also check the logfiles for possible failures.
If the manual method works, try setting up your mobile phone! :)

View file

@ -0,0 +1,268 @@
<?php
/***********************************************
* File : autodiscover.php
* Project : Z-Push
* Descr : The autodiscover service for Z-Push.
*
* Created : 14.05.2014
*
* Copyright 2007 - 2014 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
include_once('../lib/core/zpushdefs.php');
include_once('../lib/exceptions/exceptions.php');
include_once('../lib/utils/utils.php');
include_once('../lib/core/zpush.php');
include_once('../lib/core/zlog.php');
include_once('../lib/interface/ibackend.php');
include_once('../lib/interface/ichanges.php');
include_once('../lib/interface/iexportchanges.php');
include_once('../lib/interface/iimportchanges.php');
include_once('../lib/interface/isearchprovider.php');
include_once('../lib/interface/istatemachine.php');
include_once('../version.php');
include_once('config.php');
class ZPushAutodiscover {
const ACCEPTABLERESPONSESCHEMA = 'http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006';
const MAXINPUTSIZE = 8192; // Bytes, the autodiscover request shouldn't exceed that value
private static $instance;
/**
* Static method to start the autodiscover process.
*
* @access public
*
* @return void
*/
public static function DoZPushAutodiscover() {
ZLog::Write(LOGLEVEL_DEBUG, '-------- Start ZPushAutodiscover');
// TODO use filterevilinput?
if (stripos($_SERVER["REQUEST_METHOD"], "GET") !== false) {
ZLog::Write(LOGLEVEL_WARN, "GET request for autodiscover. Exiting.");
if (!headers_sent()) {
ZPush::PrintZPushLegal('GET not supported');
}
ZLog::Write(LOGLEVEL_DEBUG, '-------- End ZPushAutodiscover');
exit(1);
}
if (!isset(self::$instance)) {
self::$instance = new ZPushAutodiscover();
}
self::$instance->DoAutodiscover();
ZLog::Write(LOGLEVEL_DEBUG, '-------- End ZPushAutodiscover');
}
/**
* Does the complete autodiscover.
* @access public
* @throws AuthenticationRequiredException if login to the backend failed.
* @throws ZPushException if the incoming XML is invalid..
*
* @return void
*/
public function DoAutodiscover() {
if (!defined('REAL_BASE_PATH')) {
define('REAL_BASE_PATH', str_replace('autodiscover/', '', BASE_PATH));
}
set_include_path(get_include_path() . PATH_SEPARATOR . REAL_BASE_PATH);
$response = "";
try {
$incomingXml = $this->getIncomingXml();
$backend = ZPush::GetBackend();
$username = $this->login($backend, $incomingXml);
$userDetails = $backend->GetUserDetails($username);
$email = ($this->getAttribFromUserDetails($userDetails, 'emailaddress')) ? $this->getAttribFromUserDetails($userDetails, 'emailaddress') : $incomingXml->Request->EMailAddress;
$userFullname = ($this->getAttribFromUserDetails($userDetails, 'fullname')) ? $this->getAttribFromUserDetails($userDetails, 'fullname') : $email;
ZLog::Write(LOGLEVEL_WBXML, sprintf("Resolved user's '%s' fullname to '%s'", $username, $userFullname));
$response = $this->createResponse($email, $userFullname);
setcookie("membername", $username);
}
catch (AuthenticationRequiredException $ex) {
if (isset($incomingXml)) {
ZLog::Write(LOGLEVEL_ERROR, sprintf("Unable to complete autodiscover because login failed for user with email '%s'", $incomingXml->Request->EMailAddress));
}
else {
ZLog::Write(LOGLEVEL_ERROR, sprintf("Unable to complete autodiscover incorrect request: '%s'", $ex->getMessage()));
}
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Basic realm="ZPush"');
http_response_code(401);
}
catch (ZPushException $ex) {
ZLog::Write(LOGLEVEL_ERROR, sprintf("Unable to complete autodiscover because of ZPushException. Error: %s", $ex->getMessage()));
if(!headers_sent()) {
header('HTTP/1.1 '. $ex->getHTTPCodeString());
foreach ($ex->getHTTPHeaders() as $h) {
header($h);
}
}
}
$this->sendResponse($response);
}
/**
* Processes the incoming XML request and parses it to a SimpleXMLElement.
*
* @access private
* @throws ZPushException if the XML is invalid.
* @throws AuthenticationRequiredException if no login data was sent.
*
* @return SimpleXMLElement
*/
private function getIncomingXml() {
if ($_SERVER['CONTENT_LENGTH'] > ZPushAutodiscover::MAXINPUTSIZE) {
throw new ZPushException('The request input size exceeds 8kb.');
}
if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW'])) {
throw new AuthenticationRequiredException();
}
$input = @file_get_contents('php://input');
$xml = simplexml_load_string($input);
if (LOGLEVEL >= LOGLEVEL_WBXML) {
ZLog::Write(LOGLEVEL_WBXML, sprintf("ZPushAutodiscover->getIncomingXml() incoming XML data:%s%s", PHP_EOL, $xml->asXML()));
}
if (!isset($xml->Request->EMailAddress)) {
throw new FatalException('Invalid input XML: no email address.');
}
if (Utils::GetLocalPartFromEmail($xml->Request->EMailAddress) != Utils::GetLocalPartFromEmail($_SERVER['PHP_AUTH_USER'])) {
ZLog::Write(LOGLEVEL_WARN, sprintf("The local part of the server auth user is different from the local part in the XML request ('%s' != '%s')",
Utils::GetLocalPartFromEmail($xml->Request->EMailAddress), Utils::GetLocalPartFromEmail($_SERVER['PHP_AUTH_USER'])));
}
if (!isset($xml->Request->AcceptableResponseSchema)) {
throw new FatalException('Invalid input XML: no AcceptableResponseSchema.');
}
if ($xml->Request->AcceptableResponseSchema != ZPushAutodiscover::ACCEPTABLERESPONSESCHEMA) {
throw new FatalException('Invalid input XML: not a mobilesync responseschema.');
}
return $xml;
}
/**
* Logins using the backend's Logon function.
*
* @param IBackend $backend
* @param String $incomingXml
* @access private
* @throws AuthenticationRequiredException if no login data was sent.
*
* @return string $username
*/
private function login($backend, $incomingXml) {
// Determine the login name depending on the configuration: complete email address or
// the local part only.
if (USE_FULLEMAIL_FOR_LOGIN) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Using the complete email address for login."));
$username = $incomingXml->Request->EMailAddress;
}
else{
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Using the username only for login."));
$username = Utils::GetLocalPartFromEmail($incomingXml->Request->EMailAddress);
}
if($backend->Logon($username, "", $_SERVER['PHP_AUTH_PW']) == false) {
throw new AuthenticationRequiredException("Access denied. Username or password incorrect.");
}
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPushAutodiscover->login() Using '%s' as the username.", $username));
return $username;
}
/**
* Creates the XML response.
*
* @param string $email
* @param string $userFullname
* @access private
*
* @return string
*/
private function createResponse($email, $userFullname) {
$xml = file_get_contents('response.xml');
$response = new SimpleXMLElement($xml);
$response->Response->User->DisplayName = $userFullname;
$response->Response->User->EMailAddress = $email;
$response->Response->Action->Settings->Server->Url = SERVERURL;
$response->Response->Action->Settings->Server->Name = SERVERURL;
$response = $response->asXML();
ZLog::Write(LOGLEVEL_WBXML, sprintf("ZPushAutodiscover->createResponse() XML response:%s%s", PHP_EOL, $response));
return $response;
}
/**
* Sends the response to the device.
* @param string $response
* @access private
*
* @return void
*/
private function sendResponse($response) {
ZLog::Write(LOGLEVEL_DEBUG, "ZPushAutodiscover->sendResponse() sending response...");
header("Content-type: text/html");
$output = fopen("php://output", "w+");
fwrite($output, $response);
fclose($output);
ZLog::Write(LOGLEVEL_DEBUG, "ZPushAutodiscover->sendResponse() response sent.");
}
/**
* Gets an attribute from user details.
* @param Array $userDetails
* @param String $attrib
* @access private
*
* @return String or false on error.
*/
private function getAttribFromUserDetails($userDetails, $attrib) {
if (isset($userDetails[$attrib]) && $userDetails[$attrib]) {
return $userDetails[$attrib];
}
ZLog::Write(LOGLEVEL_WARN, sprintf("The backend was not able to find attribute '%s' of the user. Fall back to the default value."));
return false;
}
}
ZPushAutodiscover::DoZPushAutodiscover();
?>

View file

@ -0,0 +1,89 @@
<?php
/***********************************************
* File : config.php
* Project : Z-Push
* Descr : Autodiscover configuration file
*
* Created : 30.07.2014
*
* Copyright 2007 - 2014 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
/**********************************************************************************
* Default settings
*/
// Defines the base path on the server
define('BASE_PATH', dirname($_SERVER['SCRIPT_FILENAME']). '/');
// The Z-Push server location for the autodiscover response
define('SERVERURL', 'https://localhost/Microsoft-Server-ActiveSync');
/*
* Whether to use the complete email address as a login name
* (e.g. user@company.com) or the username only (user).
* Possible values:
* false - use the username only (default).
* true - use the complete email address.
*/
define('USE_FULLEMAIL_FOR_LOGIN', false);
/**********************************************************************************
* Logging settings
* Possible LOGLEVEL and LOGUSERLEVEL values are:
* LOGLEVEL_OFF - no logging
* LOGLEVEL_FATAL - log only critical errors
* LOGLEVEL_ERROR - logs events which might require corrective actions
* LOGLEVEL_WARN - might lead to an error or require corrective actions in the future
* LOGLEVEL_INFO - usually completed actions
* LOGLEVEL_DEBUG - debugging information, typically only meaningful to developers
* LOGLEVEL_WBXML - also prints the WBXML sent to/from the device
* LOGLEVEL_DEVICEID - also prints the device id for every log entry
* LOGLEVEL_WBXMLSTACK - also prints the contents of WBXML stack
*
* The verbosity increases from top to bottom. More verbose levels include less verbose
* ones, e.g. setting to LOGLEVEL_DEBUG will also output LOGLEVEL_FATAL, LOGLEVEL_ERROR,
* LOGLEVEL_WARN and LOGLEVEL_INFO level entries.
*/
define('LOGFILEDIR', '/var/log/z-push/');
define('LOGFILE', LOGFILEDIR . 'autodiscover.log');
define('LOGERRORFILE', LOGFILEDIR . 'autodiscover-error.log');
define('LOGLEVEL', LOGLEVEL_INFO);
define('LOGUSERLEVEL', LOGLEVEL);
/**********************************************************************************
* Backend settings
*/
// the backend data provider
define('BACKEND_PROVIDER', '');
?>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006">
<Culture>en:us</Culture>
<User>
<DisplayName></DisplayName>
<EMailAddress></EMailAddress>
</User>
<Action>
<Settings>
<Server>
<Type>MobileSync</Type>
<Url></Url>
<Name></Name>
</Server>
</Settings>
</Action>
</Response>
</Autodiscover>

View file

@ -0,0 +1,3 @@
The Author of this backend is dupondje, I could have modified it.
You can found the original code here:
https://github.com/dupondje/PHP-Push-2

View file

@ -0,0 +1,5 @@
REQUIREMENTS:
php-curl
libawl-php
CalDAV server (DAViCal, Sabredav, Sogo, Owncloud...)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,67 @@
<?php
/***********************************************
* File : config.php
* Project : Z-Push
* Descr : CalDAV backend configuration file
*
* Created : 27.11.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
// ************************
// BackendCalDAV settings
// ************************
// Server address
define('CALDAV_SERVER', 'http://calendar.domain.com');
// Port
define('CALDAV_PORT', '80');
// Path
define('CALDAV_PATH', '/caldav.php/%u/');
// Default CalDAV folder (calendar folder/principal). This will be marked as the default calendar in the mobile
define('CALDAV_PERSONAL', 'home');
// If the CalDAV server supports the sync-collection operation
// DAViCal, SOGo and SabreDav support it
// SabreDav version must be at least 1.9.0, otherwise set this to false
// Setting this to false will work with most servers, but it will be slower
define('CALDAV_SUPPORTS_SYNC', false);
?>

View file

@ -0,0 +1,12 @@
This is a CardDAV backend based in the vcarddir backend.
It supports DAViCal, Sogo, OwnCloud, SabreDav... and should works with any carddav server. So if it doesn't work with your server, please open a issue.
It supports ChangesSink method that will detect and send faster changes to your device.
DAViCal implements the SYNC operation, it's a very fast method to detect changes in your vcards.
The others servers don't implement it, so the code will fallback to a slower method (suggest your carddav server developers to implement it!!).
This is controlled with a flag in the config.php file.
Also, it can autodetect multiple addressbooks and will present them to the mobile device as an unique addressbook (only iOS supports multiple addressbook).

View file

@ -0,0 +1,5 @@
REQUIREMENTS:
php-curl
php-xsl
CardDAV server (DAViCal, Sabredav, Sogo, Owncloud...)

View file

@ -0,0 +1,8 @@
*Drenalina SRL (www.drenalina.com)* sponsored the development of the following features in the BackendCardDAV, any existing bug it's my fault not theirs ;-)
Thank you very much for helping to improve it!!
- Autodetecting addressbooks within a DAV principal.
- Merging multiple addressbooks so the device will see a unique one. Only iOS based devices support multiple addressbooks, so we will merge them for now.
- Selecting default addressbook to store new contacts created from the device.
- GAL addressbook and GAL search.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,109 @@
<?php
/***********************************************
* File : config.php
* Project : Z-Push
* Descr : CardDAV backend configuration file
*
* Created : 16.03.2013
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
// ************************
// BackendCardDAV settings
// ************************
// Server protocol: http or https
define('CARDDAV_PROTOCOL', 'https');
// Server name
define('CARDDAV_SERVER', 'localhost');
// Server port
define('CARDDAV_PORT', '443');
// Server path to the addressbook, or the principal with the addressbooks
// If your user has more than 1 addressbook point it to the principal.
// Example: user test@domain.com will have 2 addressbooks
// http://localhost/caldav.php/test@domain.com/addresses/personal
// http://localhost/caldav.php/test@domain.com/addresses/work
// You set the CARDDAV_PATH to '/caldav.php/%u/addresses/' and personal and work will be autodiscovered
// %u: replaced with the username
// %d: replaced with the domain
// Add the trailing /
define('CARDDAV_PATH', '/caldav.php/%u/');
// Server path to the default addressbook
// Mobile device will create new contacts here. It must be under CARDDAV_PATH
// %u: replaced with the username
// %d: replaced with the domain
// Add the trailing /
define('CARDDAV_DEFAULT_PATH', '/caldav.php/%u/addresses/');
// Server path to the GAL addressbook. This addressbook is readonly and searchable by the user, but it will NOT be synced.
// If you don't want GAL, comment it
// %u: replaced with the username
// %d: replaced with the domain
// Add the trailing /
define('CARDDAV_GAL_PATH', '/caldav.php/%d/GAL/');
// Minimal length for the search pattern to do the real search.
define('CARDDAV_GAL_MIN_LENGTH', 5);
// Addressbook display name, the name showed in the mobile device
// %u: replaced with the username
// %d: replaced with the domain
define('CARDDAV_CONTACTS_FOLDER_NAME', '%u Addressbook');
// If the CardDAV server supports the sync-collection operation
// DAViCal and SabreDav support it, but Owncloud, SOGo don't
// SabreDav version must be at least 1.9.0, otherwise set this to false
// Setting this to false will work with most servers, but it will be slower: 1 petition for the href of vcards, and 1 petition for each vcard
define('CARDDAV_SUPPORTS_SYNC', false);
// If the CardDAV server supports the FN attribute for searches
// DAViCal supports it, but SabreDav, Owncloud and SOGo don't
// Setting this to true will search by FN. If false will search by sn, givenName and email
// It's safe to leave it as false
define('CARDDAV_SUPPORTS_FN_SEARCH', false);
// If your carddav server needs to use file extension to recover a vcard.
// Davical needs it
// SOGo official demo online needs it, but some SOGo installation don't need it, so test it
define('CARDDAV_URL_VCARD_EXTENSION', '.vcf');

View file

@ -57,11 +57,12 @@ require_once("backend/combined/config.php");
require_once("backend/combined/importer.php"); require_once("backend/combined/importer.php");
require_once("backend/combined/exporter.php"); require_once("backend/combined/exporter.php");
class BackendCombined extends Backend { class BackendCombined extends Backend implements ISearchProvider {
public $config; public $config;
public $backends; public $backends;
private $activeBackend; private $activeBackend;
private $activeBackendID; private $activeBackendID;
private $numberChangesSink;
/** /**
* Constructor of the combined backend * Constructor of the combined backend
@ -72,12 +73,12 @@ class BackendCombined extends Backend {
parent::Backend(); parent::Backend();
$this->config = BackendCombinedConfig::GetBackendCombinedConfig(); $this->config = BackendCombinedConfig::GetBackendCombinedConfig();
foreach ($this->config['backends'] as $i => $b){ $backend_values = array_unique(array_values($this->config['folderbackend']));
// load and instatiate backend foreach ($backend_values as $i) {
ZPush::IncludeBackend($b['name']); ZPush::IncludeBackend($this->config['backends'][$i]['name']);
$this->backends[$i] = new $b['name'](); $this->backends[$i] = new $this->config['backends'][$i]['name']();
} }
ZLog::Write(LOGLEVEL_INFO, sprintf("Combined %d backends loaded.", count($this->backends))); ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined %d backends loaded.", count($this->backends)));
} }
/** /**
@ -116,7 +117,8 @@ class BackendCombined extends Backend {
return false; return false;
} }
} }
ZLog::Write(LOGLEVEL_INFO, "Combined->Logon() success");
ZLog::Write(LOGLEVEL_DEBUG, "Combined->Logon() success");
return true; return true;
} }
@ -154,7 +156,7 @@ class BackendCombined extends Backend {
return false; return false;
} }
} }
ZLog::Write(LOGLEVEL_INFO, "Combined->Setup() success"); ZLog::Write(LOGLEVEL_DEBUG, "Combined->Setup() success");
return true; return true;
} }
@ -274,6 +276,10 @@ class BackendCombined extends Backend {
*/ */
public function SendMail($sm) { public function SendMail($sm) {
ZLog::Write(LOGLEVEL_DEBUG, "Combined->SendMail()"); ZLog::Write(LOGLEVEL_DEBUG, "Combined->SendMail()");
// Convert source folderid
if (isset($sm->source->folderid)) {
$sm->source->folderid = $this->GetBackendFolder($sm->source->folderid);
}
foreach ($this->backends as $i => $b){ foreach ($this->backends as $i => $b){
if($this->backends[$i]->SendMail($sm) == true){ if($this->backends[$i]->SendMail($sm) == true){
return true; return true;
@ -358,11 +364,125 @@ class BackendCombined extends Backend {
* @return string id of the created/updated calendar obj * @return string id of the created/updated calendar obj
* @throws StatusException * @throws StatusException
*/ */
public function MeetingResponse($requestid, $folderid, $error) { public function MeetingResponse($requestid, $folderid, $response) {
$backend = $this->GetBackend($folderid); $backend = $this->GetBackend($folderid);
if($backend === false) if($backend === false)
return false; return false;
return $backend->MeetingResponse($requestid, $this->GetBackendFolder($folderid), $error); return $backend->MeetingResponse($requestid, $this->GetBackendFolder($folderid), $response);
}
/**
* Deletes all contents of the specified folder.
* This is generally used to empty the trash (wastebasked), but could also be used on any
* other folder.
*
* @param string $folderid
* @param boolean $includeSubfolders (opt) also delete sub folders, default true
*
* @access public
* @return boolean
* @throws StatusException
*/
public function EmptyFolder($folderid, $includeSubfolders = true) {
$backend = $this->GetBackend($folderid);
if($backend === false)
return false;
return $backend->EmptyFolder($this->GetBackendFolder($folderid), $includeSubfolders);
}
/**
* Indicates if the backend has a ChangesSink.
* A sink is an active notification mechanism which does not need polling.
*
* @access public
* @return boolean
*/
public function HasChangesSink() {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCombined->HasChangesSink()"));
$this->numberChangesSink = 0;
foreach ($this->backends as $i => $b) {
if ($this->backends[$i]->HasChangesSink()) {
$this->numberChangesSink++;
}
}
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCombined->HasChangesSink - Number ChangesSink found: %d", $this->numberChangesSink));
return true;
}
/**
* The folder should be considered by the sink.
* Folders which were not initialized should not result in a notification
* of IBacken->ChangesSink().
*
* @param string $folderid
*
* @access public
* @return boolean false if there is any problem with that folder
*/
public function ChangesSinkInitialize($folderid) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCombined->ChangesSinkInitialize('%s')", $folderid));
$backend = $this->GetBackend($folderid);
if($backend === false) {
// if not backend is found we return true, we don't want this to never cause an error
return true;
}
if ($backend->HasChangesSink()) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCombined->ChangesSinkInitialize('%s') is supported, initializing", $folderid));
return $backend->ChangesSinkInitialize($this->GetBackendFolder($folderid));
}
else {
// if the backend doesn't support ChangesSink, we also return true so we don't get an error
return true;
}
}
/**
* The actual ChangesSink.
* For max. the $timeout value this method should block and if no changes
* are available return an empty array.
* If changes are available a list of folderids is expected.
*
* @param int $timeout max. amount of seconds to block
*
* @access public
* @return array
*/
public function ChangesSink($timeout = 30) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCombined->ChangesSink(%d)", $timeout));
$notifications = array();
if ($this->numberChangesSink == 0) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCombined doesn't include any Sinkable backends"));
} else {
$stopat = time() + $timeout - 1;
//we will spend 2 seconds at least in each backend that support changessink
// why 2 seconds? because it's the minimum to ensure we run at least once the changessink
// I think it's fairer than run for 10 continuos seconds the same backend (run backend1, run backend2, run backend1, run backend2... vs run backend1, run backend1, run backend2, run backend2)
do {
foreach ($this->backends as $i => $b) {
if ($this->backends[$i]->HasChangesSink()) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCombined->ChangesSink - Calling in '%s' with %d", get_class($b), 2));
// 2 seconds hardcoded timeout!!!
$notifications_backend = $this->backends[$i]->ChangesSink(2);
//preppend backend delimiter
for ($c = 0; $c < count($notifications_backend); $c++) {
$notifications_backend[$c] = $i . $this->config['delimiter'] . $notifications_backend[$c];
}
$notifications = array_merge($notifications, $notifications_backend);
}
}
} while($stopat > time() && empty($notifications));
}
return $notifications;
} }
/** /**
@ -413,7 +533,159 @@ class BackendCombined extends Backend {
$pos = strpos($folderid, $this->config['delimiter']); $pos = strpos($folderid, $this->config['delimiter']);
if($pos === false) if($pos === false)
return false; return false;
return substr($folderid,0,$pos); return substr($folderid, 0, $pos);
}
/**
* Indicates which AS version is supported by the backend.
* Return the lowest version supported by the backends used.
*
* @access public
* @return string AS version constant
*/
public function GetSupportedASVersion() {
$version = ZPush::ASV_14;
foreach ($this->backends as $i => $b) {
$subversion = $this->backends[$i]->GetSupportedASVersion();
if ($subversion < $version) {
$version = $subversion;
}
}
return $version;
}
/**
* Returns the BackendCombined as it implements the ISearchProvider interface
* This could be overwritten by the global configuration
*
* @access public
* @return object Implementation of ISearchProvider
*/
public function GetSearchProvider() {
return $this;
}
/*-----------------------------------------------------------------------------------------
-- ISearchProvider
------------------------------------------------------------------------------------------*/
/**
* Indicates if a search type is supported by this SearchProvider
* It supports all the search types, searches are delegated.
*
* @param string $searchtype
*
* @access public
* @return boolean
*/
public function SupportsType($searchtype) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined->SupportsType('%s')", $searchtype));
$i = $this->getSearchBackend($searchtype);
return $i !== false;
}
/**
* Queries the LDAP backend
*
* @param string $searchquery string to be searched for
* @param string $searchrange specified searchrange
*
* @access public
* @return array search results
*/
public function GetGALSearchResults($searchquery, $searchrange) {
ZLog::Write(LOGLEVEL_DEBUG, "Combined->GetGALSearchResults()");
$i = $this->getSearchBackend(ISearchProvider::SEARCH_GAL);
$result = false;
if ($i !== false) {
$result = $this->backends[$i]->GetGALSearchResults($searchquery, $searchrange);
}
return $result;
}
/**
* Searches for the emails on the server
*
* @param ContentParameter $cpo
*
* @return array
*/
public function GetMailboxSearchResults($cpo) {
ZLog::Write(LOGLEVEL_DEBUG, "Combined->GetMailboxSearchResults()");
$i = $this->getSearchBackend(ISearchProvider::SEARCH_MAILBOX);
$result = false;
if ($i !== false) {
//Convert $cpo GetSearchFolderid
$cpo->SetSearchFolderid($this->GetBackendFolder($cpo->GetSearchFolderid()));
$result = $this->backends[$i]->GetMailboxSearchResults($cpo, $i . $this->config['delimiter']);
}
return $result;
}
/**
* Terminates a search for a given PID
*
* @param int $pid
*
* @return boolean
*/
public function TerminateSearch($pid) {
ZLog::Write(LOGLEVEL_DEBUG, "Combined->TerminateSearch()");
foreach ($this->backends as $i => $b) {
if ($this->backends[$i] instanceof ISearchProvider) {
$this->backends[$i]->TerminateSearch($pid);
}
}
return true;
}
/**
* Disconnects backends
*
* @access public
* @return boolean
*/
public function Disconnect() {
ZLog::Write(LOGLEVEL_DEBUG, "Combined->Disconnect()");
foreach ($this->backends as $i => $b) {
if ($this->backends[$i] instanceof ISearchProvider) {
$this->backends[$i]->Disconnect();
}
}
return true;
}
/**
* Returns the first backend that support a search type
*
* @param string $searchtype
*
* @access private
* @return string
*/
private function getSearchBackend($searchtype) {
foreach ($this->backends as $i => $b) {
if ($this->backends[$i] instanceof ISearchProvider) {
if ($this->backends[$i]->SupportsType($searchtype)) {
return $i;
}
}
}
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined->getSearchBackend('%s') No support found!", $searchtype));
return false;
} }
} }
?> ?>

View file

@ -74,6 +74,15 @@ class BackendCombinedConfig {
'v' => array( 'v' => array(
'name' => 'BackendVCardDir', 'name' => 'BackendVCardDir',
), ),
'c' => array(
'name' => 'BackendCalDAV',
),
'l' => array(
'name' => 'BackendLDAP',
),
'd' => array(
'name' => 'BackendCardDAV',
),
), ),
'delimiter' => '/', 'delimiter' => '/',
//force one type of folder to one backend //force one type of folder to one backend

View file

@ -173,7 +173,7 @@ class ExportChangesCombined implements IExportChanges {
public function InitializeExporter(&$importer) { public function InitializeExporter(&$importer) {
ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->InitializeExporter(...)"); ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->InitializeExporter(...)");
foreach ($this->exporters as $i => $e) { foreach ($this->exporters as $i => $e) {
if(!isset($this->_importwraps[$i])){ if(!isset($this->importwraps[$i])){
$this->importwraps[$i] = new ImportHierarchyChangesCombinedWrap($i, $this->backend, $importer); $this->importwraps[$i] = new ImportHierarchyChangesCombinedWrap($i, $this->backend, $importer);
} }
$e->InitializeExporter($this->importwraps[$i]); $e->InitializeExporter($this->importwraps[$i]);

View file

@ -131,6 +131,24 @@ class ImportChangesCombined implements IImportChanges {
return $this->icc->ImportMessageReadFlag($id, $flags); return $this->icc->ImportMessageReadFlag($id, $flags);
} }
/**
* Imports a change in 'star' flag
* This can never conflict
*
* @param string $id
* @param int $flags
*
* @access public
* @return boolean
*/
public function ImportMessageStarFlag($id, $flags) {
if (!$this->icc) {
ZLog::Write(LOGLEVEL_ERROR, "ImportChangesCombined->ImportMessageReadFlag() icc not configured");
return false;
}
return $this->icc->ImportMessageStarFlag($id, $flags);
}
/** /**
* Imports a move of a message. This occurs when a user moves an item to another folder * Imports a move of a message. This occurs when a user moves an item to another folder
* *

View file

@ -0,0 +1,28 @@
BackendIMAP - NOTES
===================
This backend support the Search operation in the mailbox.
Since the IMAP search operation is pretty slow, with a medium/big mailbox, or with a lots of folders,
the mobile device will timeout the operation before this is completed on server.
I'm using Dovecot + FTS-SOLR plugin so the real search is done against an Apache SOLR server.
It reduces a 1-2 minutes search to 1-5 seconds, and the response is given to the mobile device in time.
CHANGESSINK
===========
It supports ChangesSink method that will detect and send faster changes to your device.
SMTP
====
You can choice between 3 methods for send mails: mail (php mail), sendmail (native binary), smtp (php smtp direct connection).
Remember to configure it in the config.php
"mail" is a sendmail wrapper in Linux.
MBCONVERT
=========
A lot of messages come with wrong encoding, to them to look better with any device you can pre-convert them to UTF-8.
You will need to install the php-mbstring module

View file

@ -0,0 +1,7 @@
REQUIREMENTS:
php-imap
php-mbstring (optional but recommended)
libawl-php
IMAP server (Dovecot, Courier...)

View file

@ -54,25 +54,107 @@ define('IMAP_PORT', 143);
// best cross-platform compatibility (see http://php.net/imap_open for options) // best cross-platform compatibility (see http://php.net/imap_open for options)
define('IMAP_OPTIONS', '/notls/norsh'); define('IMAP_OPTIONS', '/notls/norsh');
// overwrite the "from" header if it isn't set when sending emails // overwrite the "from" header with some value
// options: 'username' - the username will be set (usefull if your login is equal to your emailaddress) // options:
// 'domain' - the value of the "domain" field is used // '' - do nothing, use the From header
// 'username' - the username will be set (usefull if your login is equal to your emailaddress)
// 'domain' - the value of the "domain" field is used
// 'sql' - the username will be the result of a sql query. REMEMBER TO INSTALL PHP-PDO AND PHP-DATABASE
// 'ldap' - the username will be the result of a ldap query. REMEMBER TO INSTALL PHP-LDAP!!
// '@mydomain.com' - the username is used and the given string will be appended // '@mydomain.com' - the username is used and the given string will be appended
define('IMAP_DEFAULTFROM', ''); define('IMAP_DEFAULTFROM', '');
// DSN: formatted PDO connection string
// mysql:host=xxx;port=xxx;dbname=xxx
// USER: username to DB
// PASSWORD: password to DB
// OPTIONS: array with options needed
// QUERY: query to execute
// FIELDS: columns in the query
// FROM: string that will be the from, replacing the column names with the values
define('IMAP_FROM_SQL_DSN', '');
define('IMAP_FROM_SQL_USER', '');
define('IMAP_FROM_SQL_PASSWORD', '');
define('IMAP_FROM_SQL_OPTIONS', serialize(array(PDO::ATTR_PERSISTENT => true)));
define('IMAP_FROM_SQL_QUERY', "select first_name, last_name, mail_address from users where mail_address = '#username@#domain'");
define('IMAP_FROM_SQL_FIELDS', serialize(array('first_name', 'last_name', 'mail_address')));
define('IMAP_FROM_SQL_FROM', '#first_name #last_name <#mail_address>');
define('IMAP_FROM_SQL_FULLNAME', '#first_name #last_name');
// SERVER: ldap server
// SERVER_PORT: ldap port
// USER: dn to use for connecting
// PASSWORD: password
// QUERY: query to execute
// FIELDS: columns in the query
// FROM: string that will be the from, replacing the field names with the values
define('IMAP_FROM_LDAP_SERVER', 'localhost');
define('IMAP_FROM_LDAP_SERVER_PORT', '389');
define('IMAP_FROM_LDAP_USER', 'cn=zpush,ou=servers,dc=zpush,dc=org');
define('IMAP_FROM_LDAP_PASSWORD', 'password');
define('IMAP_FROM_LDAP_BASE', 'dc=zpush,dc=org');
define('IMAP_FROM_LDAP_QUERY', '(mail=#username@#domain)');
define('IMAP_FROM_LDAP_FIELDS', serialize(array('givenname', 'sn', 'mail')));
define('IMAP_FROM_LDAP_FROM', '#givenname #sn <#mail>');
define('IMAP_FROM_LDAP_FULLNAME', '#givenname #sn');
// Root folder or prefix in your IMAP server (without the separator). For example, with courier it will be INBOX, and your folder will be INBOX.Sent
// You can use the real case
define('IMAP_FOLDER_ROOT', 'INBOX');
// copy outgoing mail to this folder. If not set z-push will try the default folders // copy outgoing mail to this folder. If not set z-push will try the default folders
define('IMAP_SENTFOLDER', ''); // You can use the real case and the full path (INBOX.Sent)
define('IMAP_FOLDER_SENT', '');
// forward messages inline (default false - as attachment) // Draft folder
define('IMAP_INLINE_FORWARD', false); // You can use the real case and the full path (INBOX.Draft)
define('IMAP_FOLDER_DRAFT', '');
// use imap_mail() to send emails (default) - if false mail() is used // Trash folder
define('IMAP_USE_IMAPMAIL', true); // You can use the real case and the full path (INBOX.Trash)
define('IMAP_FOLDER_TRASH', '');
// forward messages inline (default true - inlined)
define('IMAP_INLINE_FORWARD', true);
/* BEGIN fmbiete's contribution r1527, ZP-319 */
// list of folders we want to exclude from sync. Names, or part of it, separated by | // list of folders we want to exclude from sync. Names, or part of it, separated by |
// example: dovecot.sieve|archive|spam // example: dovecot.sieve|archive|spam
define('IMAP_EXCLUDED_FOLDERS', ''); define('IMAP_EXCLUDED_FOLDERS', '');
/* END fmbiete's contribution r1527, ZP-319 */
?>
// Method used for sending mail
// mail => mail() php function
// sendmail => sendmail executable
// smtp => direct connection against SMTP
define('IMAP_SMTP_METHOD', 'mail');
global $imap_smtp_params;
// SMTP Parameters
// mail : no params
$imap_smtp_params = array();
// sendmail
//$imap_smtp_params = array('sendmail_path' => '/usr/bin/sendmail', 'sendmail_args' => '-i');
// smtp
// "host" - The server to connect. Default is localhost.
// "port" - The port to connect. Default is 25.
// "auth" - Whether or not to use SMTP authentication. Default is FALSE.
// "username" - The username to use for SMTP authentication. "imap_username" for using the same username as the imap server
// "password" - The password to use for SMTP authentication. "imap_password" for using the same password as the imap server
// "localhost" - The value to give when sending EHLO or HELO. Default is localhost
// "timeout" - The SMTP connection timeout. Default is NULL (no timeout).
// "verp" - Whether to use VERP or not. Default is FALSE.
// "debug" - Whether to enable SMTP debug mode or not. Default is FALSE.
// "persist" - Indicates whether or not the SMTP connection should persist over multiple calls to the send() method.
// "pipelining" - Indicates whether or not the SMTP commands pipelining should be used.
//$imap_smtp_params = array('host' => 'localhost', 'port' => 25, 'auth' => false);
// If you want to use SSL with port 25 or port 465 you must preppend "ssl://" before the hostname or IP of your SMTP server
// IMPORTANT: To use SSL you must use PHP 5.1 or later, install openssl libs and use ssl:// within the host variable
//$imap_smtp_params = array('host' => 'ssl://localhost', 'port' => 465, 'auth' => true, 'username' => 'imap_username', 'password' => 'imap_password');
// If you are using IMAP_SMTP_METHOD = mail or sendmail and your sent messages are not correctly displayed you can change this to "\n".
// BUT, it doesn't comply with RFC 2822 and will break if using smtp method
define('MAIL_MIMEPART_CRLF', "\r\n");
?>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,295 @@
<?php
/**
* Add extra parts (not text; inlined or attached parts) to a mimepart object.
*
* @param Mail_mimePart $email reference to the object
* @param array $parts array of parts
*
* @access private
* @return void
*/
function add_extra_sub_parts(&$email, $parts) {
if (isset($parts)) {
foreach ($parts as $part) {
$new_part = null;
// Only if it's an attachment we will add the text parts, because all the inline/no disposition have been already added
if (isset($part->disposition) && $part->disposition == "attachment") {
// it's an attachment
$new_part = add_sub_part($email, $part);
}
else {
if (isset($part->ctype_primary) && $part->ctype_primary != "text" && $part->ctype_primary != "multipart") {
// it's not a text part or a multipart
$new_part = add_sub_part($email, $part);
}
}
if (isset($part->parts)) {
// We add sub-parts to the new part (if any), not to the main message. Recursive calling
if ($new_part === null) {
add_extra_sub_parts($email, $part->parts);
}
else {
add_extra_sub_parts($new_part, $part->parts);
}
}
}
}
}
/**
* Add a subpart to a mimepart object.
*
* @param Mail_mimePart $email reference to the object
* @param object $part message part
*
* @access private
* @return void
*/
function add_sub_part(&$email, $part) {
//http://tools.ietf.org/html/rfc4021
$new_part = null;
$params = array();
$params['content_type'] = '';
if (isset($part) && isset($email)) {
if (isset($part->ctype_primary)) {
$params['content_type'] = $part->ctype_primary;
}
if (isset($part->ctype_secondary)) {
$params['content_type'] .= '/' . $part->ctype_secondary;
}
if (isset($part->ctype_parameters)) {
foreach ($part->ctype_parameters as $k => $v) {
if(strcasecmp($k, 'boundary') != 0) {
$params['content_type'] .= '; ' . $k . '=' . $v;
}
}
}
if (isset($part->disposition)) {
$params['disposition'] = $part->disposition;
}
//FIXME: dfilename => filename
if (isset($part->d_parameters)) {
foreach ($part->d_parameters as $k => $v) {
$params[$k] = $v;
}
}
foreach ($part->headers as $k => $v) {
switch($k) {
case "content-description":
$params['description'] = $v;
break;
case "content-type":
case "content-disposition":
case "content-transfer-encoding":
// Do nothing, we already did
break;
case "content-id":
$params['cid'] = str_replace('<', '', str_replace('>', '', $v));
break;
default:
$params[$k] = $v;
break;
}
}
// If not exist body, the part will be multipart/alternative, so we don't add encoding
if (!isset($params['encoding']) && isset($part->body)) {
$params['encoding'] = 'base64';
}
// We could not have body; recursive messages
$new_part = $email->addSubPart(isset($part->body) ? $part->body : "", $params);
unset($params);
}
// return the new part
return $new_part;
}
/**
* Add a subpart to a mimepart object.
*
* @param Mail_mimePart $email reference to the object
* @param object $part message part
*
* @access private
* @return void
*/
function change_charset_and_add_subparts(&$email, $part) {
if (isset($part)) {
$new_part = null;
if (isset($part->ctype_parameters['charset'])) {
$part->ctype_parameters['charset'] = 'UTF-8';
$new_part = add_sub_part($email, $part);
}
else {
// We don't add the charset because it could be a non-text part
$new_part = add_sub_part($email, $part);
}
if (isset($part->parts)) {
foreach ($part->parts as $subpart) {
// Subparts are added to the part, not the main message
change_charset_and_add_subparts($new_part, $subpart);
}
}
}
}
/**
* Creates a MIME message from a decoded MIME message, reencoding and fixing the text.
*
* @param array $message array returned from Mail_mimeDecode->decode
*
* @access public
* @return string MIME message
*/
function build_mime_message($message) {
$finalEmail = new Mail_mimePart(isset($message->body) ? $message->body : "", array('headers' => $message->headers));
if (isset($message->parts)) {
foreach ($message->parts as $part) {
change_charset_and_add_subparts($finalEmail, $part);
}
}
$mimeHeaders = Array();
$mimeHeaders['headers'] = Array();
$is_mime = false;
foreach ($message->headers as $key => $value) {
switch($key) {
case 'content-type':
$new_value = $message->ctype_primary . "/" . $message->ctype_secondary;
$is_mime = (strcasecmp($message->ctype_primary, 'multipart') == 0);
if (isset($message->ctype_parameters)) {
foreach ($message->ctype_parameters as $ckey => $cvalue) {
switch($ckey) {
case 'charset':
$new_value .= '; charset="UTF-8"';
break;
case 'boundary':
// Do nothing, we are encoding also the headers
break;
default:
$new_value .= '; ' . $ckey . '="' . $cvalue . '"';
break;
}
}
}
$mimeHeaders['content_type'] = $new_value;
break;
case 'content-transfer-encoding':
if (strcasecmp($value, "base64") == 0 || strcasecmp($value, "binary") == 0) {
$mimeHeaders['encoding'] = "base64";
}
else {
$mimeHeaders['encoding'] = "8bit";
}
break;
case 'content-id':
$mimeHeaders['cid'] = $value;
break;
case 'content-location':
$mimeHeaders['location'] = $value;
break;
case 'content-disposition':
$mimeHeaders['disposition'] = $value;
break;
case 'content-description':
$mimeHeaders['description'] = $value;
break;
default:
if (is_array($value)) {
foreach($value as $v) {
$mimeHeaders['headers'][$key] = $v;
}
}
else {
$mimeHeaders['headers'][$key] = $value;
}
break;
}
}
$finalEmail = new Mail_mimePart(isset($message->body) ? $message->body : "", $mimeHeaders);
unset($mimeHeaders['headers']);
unset($mimeHeaders);
if (isset($message->parts)) {
foreach ($message->parts as $part) {
change_charset_and_add_subparts($finalEmail, $part);
}
}
$boundary = '=_' . md5(rand() . microtime());
$finalEmail = $finalEmail->encode($boundary);
$headers = "";
$mimePart = new Mail_mimePart();
foreach ($finalEmail['headers'] as $key => $value) {
if (is_array($value)) {
foreach ($values as $ikey => $ivalue) {
$headers .= $key . ": " . $mimePart->encodeHeader($key, $ivalue, "utf-8", "base64") . "\n";
}
}
else {
$headers .= $key . ": " . $mimePart->encodeHeader($key, $value, "utf-8", "base64") . "\n";
}
}
unset($mimePart);
if ($is_mime) {
$built_message = "$headers\nThis is a multi-part message in MIME format.\n".$finalEmail['body'];
}
else {
$built_message = "$headers\n".$finalEmail['body'];
}
unset($headers);
unset($finalEmail);
return $built_message;
}
/**
* Detect if the message-part is VCALENDAR
* Content-Type: text/calendar;
*
* @param Mail_mimeDecode $message
* @return boolean
* @access public
*/
function is_calendar($message) {
$res = false;
if (isset($message->ctype_primary) && isset($message->ctype_secondary)) {
if ($message->ctype_primary == "text" && $message->ctype_secondary == "calendar") {
$res = true;
}
}
return $res;
}
/**
* Detect if the message-part is SMIME
* Content-Type: multipart/signed;
* Content-Type: application/pkcs7-mime;
*
* @param Mail_mimeDecode $message
* @return boolean
* @access public
*/
function is_smime($message) {
$res = false;
if (isset($message->ctype_primary) && isset($message->ctype_secondary)) {
if (($message->ctype_primary == "multipart" && $message->ctype_secondary == "signed") || ($message->ctype_primary == "application" && $message->ctype_secondary == "pkcs7-mime")) {
$res = true;
}
}
return $res;
}

View file

@ -0,0 +1,3 @@
The Author of this backend is dupondje, I could have modified it.
You can found the original code here:
https://github.com/dupondje/PHP-Push-2

View file

@ -0,0 +1,60 @@
<?php
/***********************************************
* File : config.php
* Project : Z-Push
* Descr : LDAP backend configuration file
*
* Created : 27.11.2012
*
* Copyright 2007 - 2012 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
// **********************
// BackendLDAP settings
// **********************
// Server address
define('LDAP_SERVER', 'localhost');
// Server Port
define('LDAP_SERVER_PORT', '389');
// LDAP USER DN
define('LDAP_USER_DN', 'uid=%u,ou=mailaccount,dc=phppush,dc=com');
// LDAP BASE DNS
define('LDAP_BASE_DNS', 'Contacts:ou=addressbook,uid=%u,ou=mailaccount,dc=phppush,dc=com'); //Multiple values separator is |
?>

View file

@ -0,0 +1,584 @@
<?php
/***********************************************
* File : ldap.php
* Project : PHP-Push
* Descr : This backend is based on
* 'BackendDiff' and implements an
* (Open)LDAP interface
*
* Created : 07.04.2012
*
* Copyright 2012 - 2014 Jean-Louis Dupond
*
* Jean-Louis Dupond released this code as AGPLv3 here: https://github.com/dupondje/PHP-Push-2/issues/93
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
// config file
require_once("backend/ldap/config.php");
include_once('lib/default/diffbackend/diffbackend.php');
class BackendLDAP extends BackendDiff {
private $ldap_link;
private $user;
public function Logon($username, $domain, $password) {
$this->user = $username;
$user_dn = str_replace('%u', $username, LDAP_USER_DN);
$this->ldap_link = ldap_connect(LDAP_SERVER, LDAP_SERVER_PORT);
ldap_set_option($this->ldap_link, LDAP_OPT_PROTOCOL_VERSION, 3);
if (ldap_bind($this->ldap_link, $user_dn, $password)) {
ZLog::Write(LOGLEVEL_INFO, sprintf("BackendLDAP->Logon(): User '%s' is authenticated on LDAP", $username));
return true;
}
else {
ZLog::Write(LOGLEVEL_INFO, sprintf("BackendLDAP->Logon(): User '%s' is not authenticated on LDAP. Error: ", $username, ldap_error($this->ldap_link)));
return false;
}
}
public function Logoff() {
if (ldap_unbind($this->ldap_link)) {
ZLog::Write(LOGLEVEL_INFO, sprintf("BackendLDAP->Logoff(): Disconnection successfull."));
}
else {
ZLog::Write(LOGLEVEL_INFO, sprintf("BackendLDAP->Logoff(): Disconnection failed. Error: %s", ldap_error($this->ldap_link)));
}
return true;
}
public function SendMail($sm) {
return false;
}
public function GetAttachmentData($attname) {
return false;
}
public function GetWasteBasket() {
return false;
}
public function GetFolderList() {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendLDAP->GetFolderList(): Getting all folders."));
$contacts = array();
$dns = explode("|", LDAP_BASE_DNS);
foreach ($dns as $dn) {
$name = explode(":", $dn);
$folder = $this->StatFolder($name[0]);
$contacts[] = $folder;
}
return $contacts;
}
public function GetFolder($id) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendLDAP->GetFolder('%s')", $id));
$folder = new SyncFolder();
$folder->serverid = $id;
$folder->parentid = "0";
$folder->displayname = $id;
$folder->type = SYNC_FOLDER_TYPE_CONTACT;
return $folder;
}
public function StatFolder($id) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendLDAP->StatFolder('%s')", $id));
$folder = $this->GetFolder($id);
$stat = array();
$stat["id"] = $id;
$stat["parent"] = $folder->parentid;
$stat["mod"] = $folder->displayname;
return $stat;
}
public function ChangeFolder($folderid, $oldid, $displayname, $type) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendLDAP->ChangeFolder('%s','%s','%s','%s')", $folderid, $oldid, $displayname, $type));
return false;
}
public function DeleteFolder($id, $parentid) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendLDAP->DeleteFolder('%s','%s')", $id, $parentid));
return false;
}
public function GetMessageList($folderid, $cutoffdate) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendLDAP->GetMessageList('%s','%s')", $folderid, $cutoffdate));
$cutoff = date("YmdHis\Z", $cutoffdate);
$filter = sprintf('(modifyTimestamp>=%s)', $cutoff);
$attributes = array("entryUUID", "modifyTimestamp");
$messages = array();
$base_dns = explode("|", LDAP_BASE_DNS);
foreach ($base_dns as $base_dn) {
$folder = explode(":", $base_dn);
if ($folder[0] == $folderid) {
$base_dn = str_replace('%u', $this->user, $folder[1]);
$results = ldap_list($this->ldap_link, $base_dn, $filter, $attributes);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendLDAP->GetMessageList(): Got %s contacts in base_dn '%s'.", ldap_count_entries($this->ldap_link, $results), $base_dn));
$entries = ldap_get_entries($this->ldap_link, $results);
for ($i = 0; $i < $entries["count"]; $i++) {
$message = array();
$message["id"] = $entries[$i]["entryuuid"][0];
$message["mod"] = $entries[$i]["modifytimestamp"][0];
$message["flags"] = "1";
$messages[] = $message;
}
}
}
return $messages;
}
public function GetMessage($folderid, $id, $contentparameters) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendLDAP->GetMessage('%s','%s')", $folderid, $id));
$truncsize = Utils::GetTruncSize($contentparameters->GetTruncation());
$base_dns = explode("|", LDAP_BASE_DNS);
foreach ($base_dns as $base_dn) {
$folder = explode(":", $base_dn);
if ($folder[0] == $folderid) {
$base_dn = str_replace('%u', $this->user, $folder[1]);
$result_id = ldap_list($this->ldap_link, $base_dn, "(entryUUID=".$id.")");
if ($result_id) {
$entry_id = ldap_first_entry($this->ldap_link, $result_id);
if ($entry_id) {
return $this->_ParseLDAPMessage($result_id, $entry_id, $truncsize);
}
}
}
}
}
private function _ParseLDAPMessage($result_id, $entry_id, $truncsize = -1) {
$contact = new SyncContact();
$values = ldap_get_attributes($this->ldap_link, $entry_id);
for ($i = 0; $i < $values["count"]; $i++) {
$name = $values[$i];
$value = $values[$name][0];
switch ($name) {
//person
case "cn":
case "fileAs":
$contact->fileas = $value;
break;
case "sn":
$contact->lastname = $value;
break;
//inetOrgPerson
case "departmentNumber":
$contact->department = $value;
break;
case "givenName":
$contact->firstname = $value;
break;
case "homePhone":
$contact->homephonenumber = $value;
if ($values[$name]["count"] >= 2) {
$contact->home2phonenumber = $values[$name][1];
}
break;
case "jpegPhoto":
$contact->picture = base64_encode($value);
break;
case "labeledURI":
$contact->webpage = $value;
break;
case "mail":
$contact->email1address = $value;
if ($values[$name]["count"] >= 2) {
$contact->email2address = $values[$name][1];
}
if ($values[$name]["count"] >= 3) {
$contact->email3address = $values[$name][2];
}
break;
case "mobile":
$contact->mobilephonenumber = $value;
break;
case "o":
$contact->companyname = $value;
break;
case "pager":
$contact->pagernumber = $value;
break;
case "secretary":
case "assistantName":
$contact->assistantname = $value;
break;
//organizationalPerson
case "l":
$contact->businesscity = $value;
break;
case "ou":
$contact->department = $value;
break;
case "physicalDeliveryOfficeName":
$contact->officelocation = $value;
break;
case "postalCode":
$contact->businesspostalcode = $value;
break;
case "st":
$contact->businessstate = $value;
break;
case "street":
$contact->businessstreet = $value;
break;
case "telephoneNumber":
$contact->businessphonenumber = $value;
if ($values[$name]["count"] >= 2) {
$contact->business2phonenumber = $values[$name][1];
}
break;
case "title":
$contact->title = $value;
break;
case "description":
case "note":
if (Request::GetProtocolVersion() >= 12.0) {
$contact->asbody = new SyncBaseBody();
$contact->asbody->type = SYNC_BODYPREFERENCE_PLAIN;
$contact->asbody->data = $value;
if ($truncsize > 0 && $truncsize < strlen($contact->asbody->data)) {
$contact->asbody->truncated = 1;
$contact->asbody->data = Utils::Utf8_truncate($contact->asbody->data, $truncsize);
}
else {
$contact->asbody->truncated = 0;
}
$contact->asbody->estimatedDataSize = strlen($contact->asbody->data);
}
else {
$contact->body = $value;
if ($truncsize > 0 && $truncsize < strlen($contact->body)) {
$contact->bodytruncated = 1;
$contact->body = Utils::Utf8_truncate($contact->body, $truncsize);
}
else {
$contact->bodytruncated = 0;
}
$contact->bodysize = strlen($contact->body);
}
break;
case "assistantPhone":
$contact->assistnamephonenumber = $value;
break;
case "birthDate":
$contact->birthday = $value;
break;
case "anniversary":
$contact->anniversary = $value;
break;
case "businessRole":
$contact->jobtitle = $value;
break;
case "carPhone":
$contact->carphonenumber = $value;
break;
case "facsimileTelephoneNumber":
$contact->businessfaxnumber = $value;
break;
case "homeFacsimileTelephoneNumber":
$contact->homefaxnumber = $value;
break;
case "spouseName":
$contact->spouse = $value;
break;
case "managerName":
$contact->managername = $value;
break;
case "radio":
$contact->radiophonenumber = $value;
break;
}
}
return $contact;
}
public function StatMessage($folderid, $id) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendLDAP->StatMessage('%s','%s')", $folderid, $id));
$base_dns = explode("|", LDAP_BASE_DNS);
foreach ($base_dns as $base_dn) {
$folder = explode(":", $base_dn);
if ($folder[0] == $folderid) {
$base_dn = str_replace('%u', $this->user, $folder[1]);
$result_id = ldap_list($this->ldap_link, $base_dn, "(entryUUID=".$id.")", array("modifyTimestamp"));
if ($result_id) {
$entry_id = ldap_first_entry($this->ldap_link, $result_id);
if ($entry_id) {
$mod = ldap_get_values($this->ldap_link, $entry_id, "modifyTimestamp");
$message = array();
$message["id"] = $id;
$message["mod"] = $mod[0];
$message["flags"] = "1";
return $message;
}
}
}
}
}
public function ChangeMessage($folderid, $id, $message, $contentParameters) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendLDAP->ChangeMessage('%s','%s')", $folderid, $id));
$base_dns = explode("|", LDAP_BASE_DNS);
foreach ($base_dns as $base_dn) {
$folder = explode(":", $base_dn);
if ($folder[0] == $folderid) {
$base_dn = str_replace('%u', $this->user, $folder[1]);
$ldap_attributes = $this->_GenerateLDAPArray($message);
$result_id = ldap_list($this->ldap_link, $base_dn, "(entryUUID=".$id.")", array("modifyTimestamp"));
if ($result_id) {
$entry_id = ldap_first_entry($this->ldap_link, $result_id);
if ($entry_id) {
$dn = ldap_get_dn($this->ldap_link, $entry_id);
// We cannot ldap_modify objectClass, but we can use ldap_mod_replace
$ldap_classes = array();
$ldap_classes['objectclass'] = Array("top", "person", "inetOrgPerson", "organizationalPerson", "evolutionPerson");
$mode = ldap_mod_replace($this->ldap_link, $dn, $ldap_classes);
$mod = ldap_modify($this->ldap_link, $dn, $ldap_attributes);
if (!$mod) {
return false;
}
return $this->StatMessage($folderid, $id);
}
else {
$uid = time() . mt_rand(100000, 999999);
$dn = "uid=" . $uid . "," . $base_dn;
$add = ldap_add($this->ldap_link, $dn, $ldap_attributes);
if (!$add) {
return false;
}
$result = ldap_read($this->ldap_link, $dn, "objectClass=*", array("entryUUID"));
$entry = ldap_first_entry($this->ldap_link, $result);
$values = ldap_get_values($this->ldap_link, $entry, "entryUUID");
$entryuuid = $values[0];
return $this->StatMessage($folderid, $entryuuid);
}
}
}
}
return false;
}
private function _GenerateLDAPArray($message) {
$ldap = array();
//Set the Object Class
$ldap["objectClass"] = Array("top", "person", "inetOrgPerson", "organizationalPerson", "evolutionPerson");
//Parse Data
if ($message->fileas) {
$ldap["cn"] = $message->fileas;
$ldap["fileAs"] = $message->fileas;
}
if ($message->lastname) {
$ldap["sn"] = $message->lastname;
}
if ($message->department) {
$ldap["departmentNumber"] = $message->department;
}
if ($message->firstname) {
$ldap["givenName"] = $message->firstname;
}
if ($message->homephonenumber) {
$ldap["homePhone"][0] = $message->homephonenumber;
}
if ($message->home2phonenumber) {
$ldap["homePhone"][1] = $message->home2phonenumber;
}
if ($message->picture) {
$ldap["jpegPhoto"] = base64_decode($message->picture);
}
if ($message->webpage) {
$ldap["labeledURI"] = $message->webpage;
}
if ($message->email1address) {
$ldap["mail"][] = $message->email1address;
}
if ($message->email2address) {
$ldap["mail"][] = $message->email2address;
}
if ($message->email3address) {
$ldap["mail"][] = $message->email3address;
}
if ($message->mobilephonenumber) {
$ldap["mobile"] = $message->mobilephonenumber;
}
if ($message->companyname) {
$ldap["o"] = $message->companyname;
}
if ($message->pagernumber) {
$ldap["pager"] = $message->pagernumber;
}
if ($message->assistantname) {
$ldap["secretary"] = $message->assistantname;
$ldap["assistantName"] = $message->assistantname;
}
if ($message->businesscity) {
$ldap["l"] = $message->businesscity;
}
if ($message->department) {
$ldap["ou"] = $message->department;
}
if ($message->officelocation) {
$ldap["physicalDeliveryOfficeName"] = $message->officelocation;
}
if ($message->businesspostalcode) {
$ldap["postalCode"] = $message->businesspostalcode;
}
if ($message->businessstate) {
$ldap["st"] = $message->businessstate;
}
if ($message->businessstreet) {
$ldap["street"] = $message->businessstreet;
}
if ($message->businessphonenumber) {
$ldap["telephoneNumber"][] = $message->businessphonenumber;
}
if ($message->business2phonenumber) {
$ldap["telephoneNumber"][] = $message->business2phonenumber;
}
if ($message->title) {
$ldap["title"] = $message->title;
}
if ($message->body) {
$ldap["description"] = $message->body;
}
if ($message->asbody) {
$ldap["description"] = $message->asbody->data;
}
if ($message->assistnamephonenumber) {
$ldap["assistantPhone"] = $message->assistnamephonenumber;
}
if ($message->birthday) {
$ldap["birthDate"] = $message->birthday;
}
if ($message->anniversary) {
$ldap["anniversary"] = $message->anniversary;
}
if ($message->jobtitle) {
$ldap["businessRole"] = $message->jobtitle;
}
if ($message->carphonenumber) {
$ldap["carPhone"] = $message->carphonenumber;
}
if ($message->businessfaxnumber) {
$ldap["facsimileTelephoneNumber"] = $message->businessfaxnumber;
}
if ($message->homefaxnumber) {
$ldap["homeFacsimileTelephoneNumber"] = $message->homefaxnumber;
}
if ($message->spouse) {
$ldap["spouseName"] = $message->spouse;
}
if ($message->managername) {
$ldap["managerName"] = $message->managername;
}
if ($message->radiophonenumber) {
$ldap["radio"] = $message->radiophonenumber;
}
return $ldap;
}
public function SetReadFlag($folderid, $id, $flags, $contentParameters) {
return false;
}
public function SetStarFlag($folderid, $id, $flags, $contentParameters) {
return false;
}
public function DeleteMessage($folderid, $id, $contentParameters) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendLDAP->DeleteMessage('%s','%s')", $folderid, $id));
$base_dns = explode("|", LDAP_BASE_DNS);
foreach ($base_dns as $base_dn) {
$folder = explode(":", $base_dn);
if ($folder[0] == $folderid) {
$base_dn = str_replace('%u', $this->user, $folder[1]);
$result_id = ldap_list($this->ldap_link, $base_dn, "(entryUUID=".$id.")", array("entryUUID"));
if ($result_id) {
$entry_id = ldap_first_entry($this->ldap_link, $result_id);
if ($entry_id) {
$dn = ldap_get_dn($this->ldap_link, $entry_id);
return ldap_delete($this->ldap_link, $dn);
}
}
}
}
return false;
}
public function MoveMessage($folderid, $id, $newfolderid, $contentParameters) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendLDAP->MoveMessage('%s','%s', '%s')", $folderid, $id, $newfolderid));
$base_dns = explode("|", LDAP_BASE_DNS);
$old = "";
$new = "";
foreach ($base_dns as $base_dn) {
$folder = explode(":", $base_dn);
if ($folder[0] == $folderid) {
$old = str_replace('%u', $this->user, $folder[1]);
}
if ($folder[0] == $newfolderid) {
$new = str_replace('%u', $this->user, $folder[1]);
}
}
$result_id = ldap_list($this->ldap_link, $old, "(entryUUID=".$id.")", array("entryUUID"));
if ($result_id) {
$entry_id = ldap_first_entry($this->ldap_link, $result_id);
if ($entry_id) {
$dn = ldap_get_dn($this->ldap_link, $entry_id);
$newdn = ldap_explode_dn($dn, 0);
return ldap_rename($this->ldap_link, $dn, $newdn[0], true);
}
}
return false;
}
/**
* Indicates which AS version is supported by the backend.
*
* @access public
* @return string AS version constant
*/
public function GetSupportedASVersion() {
return ZPush::ASV_14;
}
}
?>

View file

@ -57,6 +57,7 @@
require_once("backend/maildir/config.php"); require_once("backend/maildir/config.php");
include_once('lib/default/diffbackend/diffbackend.php'); include_once('lib/default/diffbackend/diffbackend.php');
include_once('include/mimeDecode.php'); include_once('include/mimeDecode.php');
require_once('include/z_RFC822.php'); require_once('include/z_RFC822.php');
@ -541,6 +542,22 @@ class BackendMaildir extends BackendDiff {
return true; return true;
} }
/**
* Changes the 'star' flag of a message on disk
*
* @param string $folderid id of the folder
* @param string $id id of the message
* @param int $flags star flag of the message
* @param ContentParameters $contentParameters
*
* @access public
* @return boolean status of the operation
* @throws StatusException could throw specific SYNC_STATUS_* exceptions
*/
public function SetStarFlag($folderid, $id, $flags, $contentParameters) {
return false;
}
/** /**
* Called when the user has requested to delete (really delete) a message * Called when the user has requested to delete (really delete) a message
* *

View file

@ -138,7 +138,7 @@ class BackendSearchLDAP implements ISearchProvider {
$querycnt = $searchresult['count']; $querycnt = $searchresult['count'];
//do not return more results as requested in range //do not return more results as requested in range
$querylimit = (($rangeend + 1) < $querycnt) ? ($rangeend + 1) : $querycnt; $querylimit = (($rangeend + 1) < $querycnt) ? ($rangeend + 1) : $querycnt;
$items['range'] = $rangestart.'-'.($querycnt-1); $items['range'] = $rangestart.'-'.($querylimit-1);
$items['searchtotal'] = $querycnt; $items['searchtotal'] = $querycnt;
$rc = 0; $rc = 0;

View file

@ -597,6 +597,22 @@ class BackendVCardDir extends BackendDiff {
return false; return false;
} }
/**
* Changes the 'star' flag of a message on disk
*
* @param string $folderid id of the folder
* @param string $id id of the message
* @param int $flags star flag of the message
* @param ContentParameters $contentParameters
*
* @access public
* @return boolean status of the operation
* @throws StatusException could throw specific SYNC_STATUS_* exceptions
*/
public function SetStarFlag($folderid, $id, $flags, $contentParameters) {
return false;
}
/** /**
* Called when the user has requested to delete (really delete) a message * Called when the user has requested to delete (really delete) a message
* *

View file

@ -452,6 +452,21 @@ class ImportChangesICS implements IImportChanges {
return true; return true;
} }
/**
* Imports a change in 'star' flag
* This can never conflict
*
* @param string $id
* @param int $flags
*
* @access public
* @return boolean
* @throws StatusException
*/
public function ImportMessageStarFlag($id, $flags) {
return false;
}
/** /**
* Imports a move of a message. This occurs when a user moves an item to another folder * Imports a move of a message. This occurs when a user moves an item to another folder

View file

@ -603,6 +603,7 @@ define('fbFree' ,0);
define('fbTentative' ,1); define('fbTentative' ,1);
define('fbBusy' ,2); define('fbBusy' ,2);
define('fbOutOfOffice' ,3); define('fbOutOfOffice' ,3);
define('fbWorkingElsewhere' ,4);
/* ICS flags */ /* ICS flags */

View file

@ -1165,6 +1165,15 @@ define('PR_EC_STATS_SESSION_LOCKED' ,mapi_prop_tag(PT_BOOLEAN,
define('PR_EC_STATS_SESSION_BUSYSTATES' ,mapi_prop_tag(PT_MV_STRING8, PR_EC_BASE+0x47)); define('PR_EC_STATS_SESSION_BUSYSTATES' ,mapi_prop_tag(PT_MV_STRING8, PR_EC_BASE+0x47));
define('PR_EC_COMPANY_NAME' ,mapi_prop_tag(PT_STRING8, PR_EC_BASE+0x48)); define('PR_EC_COMPANY_NAME' ,mapi_prop_tag(PT_STRING8, PR_EC_BASE+0x48));
/* user features */
define('PR_EC_ENABLED_FEATURES' ,mapi_prop_tag(PT_MV_TSTRING, PR_EC_BASE+0xB3));
define('PR_EC_ENABLED_FEATURES_A' ,mapi_prop_tag(PT_MV_STRING8, PR_EC_BASE+0xB3));
define('PR_EC_ENABLED_FEATURES_W' ,mapi_prop_tag(PT_MV_UNICODE, PR_EC_BASE+0xB3));
define('PR_EC_DISABLED_FEATURES' ,mapi_prop_tag(PT_MV_TSTRING, PR_EC_BASE+0xB4));
define('PR_EC_DISABLED_FEATURES_A' ,mapi_prop_tag(PT_MV_STRING8, PR_EC_BASE+0xB4));
define('PR_EC_DISABLED_FEATURES_W' ,mapi_prop_tag(PT_MV_UNICODE, PR_EC_BASE+0xB4));
/* WA properties */ /* WA properties */
define('PR_EC_WA_ATTACHMENT_HIDDEN_OVERRIDE' ,mapi_prop_tag(PT_BOOLEAN, PR_EC_BASE+0xE0)); define('PR_EC_WA_ATTACHMENT_HIDDEN_OVERRIDE' ,mapi_prop_tag(PT_BOOLEAN, PR_EC_BASE+0xE0));

View file

@ -224,7 +224,7 @@ class MAPIMapping {
"responserequested" => PR_RESPONSE_REQUESTED, "responserequested" => PR_RESPONSE_REQUESTED,
// timezone // timezone
"alldayevent" => "PT_BOOLEAN:PSETID_Appointment:0x8215", "alldayevent" => "PT_BOOLEAN:PSETID_Appointment:0x8215",
"busystatus" => "PT_LONG:PSETID_Appointment:0x8205", "busystatus" => "PT_LONG:PSETID_Appointment:0x8224",
"rtf" => PR_RTF_COMPRESSED, "rtf" => PR_RTF_COMPRESSED,
"dtstamp" => PR_LAST_MODIFICATION_TIME, "dtstamp" => PR_LAST_MODIFICATION_TIME,
"endtime" => "PT_SYSTIME:PSETID_Appointment:0x820e", "endtime" => "PT_SYSTIME:PSETID_Appointment:0x820e",

View file

@ -231,7 +231,7 @@ class MAPIProvider {
// set server default timezone (correct timezone should be configured!) // set server default timezone (correct timezone should be configured!)
$tz = TimezoneUtil::GetFullTZ(); $tz = TimezoneUtil::GetFullTZ();
} }
$message->timezone = base64_encode($this->getSyncBlobFromTZ($tz)); $message->timezone = base64_encode(TimezoneUtil::GetSyncBlobFromTZ($tz));
if(isset($messageprops[$appointmentprops["isrecurring"]]) && $messageprops[$appointmentprops["isrecurring"]]) { if(isset($messageprops[$appointmentprops["isrecurring"]]) && $messageprops[$appointmentprops["isrecurring"]]) {
// Process recurrence // Process recurrence
@ -314,6 +314,11 @@ class MAPIProvider {
if (!isset($message->nativebodytype)) $message->nativebodytype = $this->getNativeBodyType($messageprops); if (!isset($message->nativebodytype)) $message->nativebodytype = $this->getNativeBodyType($messageprops);
// If the user is working from a location other than the office the busystatus should be interpreted as free.
if (isset($message->busystatus) && $message->busystatus == fbWorkingElsewhere) {
$message->busystatus = fbFree;
}
return $message; return $message;
} }
@ -459,6 +464,10 @@ class MAPIProvider {
if(!isset($syncMessage->exceptions)) if(!isset($syncMessage->exceptions))
$syncMessage->exceptions = array(); $syncMessage->exceptions = array();
// If the user is working from a location other than the office the busystatus should be interpreted as free.
if (isset($exception->busystatus) && $exception->busystatus == fbWorkingElsewhere) {
$exception->busystatus = fbFree;
}
array_push($syncMessage->exceptions, $exception); array_push($syncMessage->exceptions, $exception);
} }
@ -544,7 +553,7 @@ class MAPIProvider {
else else
$tz = $this->getGMTTZ(); $tz = $this->getGMTTZ();
$message->meetingrequest->timezone = base64_encode($this->getSyncBlobFromTZ($tz)); $message->meetingrequest->timezone = base64_encode(TimezoneUtil::GetSyncBlobFromTZ($tz));
// send basedate if exception // send basedate if exception
if(isset($props[$meetingrequestproperties["recReplTime"]]) || if(isset($props[$meetingrequestproperties["recReplTime"]]) ||
@ -624,6 +633,7 @@ class MAPIProvider {
$req->processMeetingCancellation(); $req->processMeetingCancellation();
} }
} }
$message->contentclass = DEFAULT_CALENDAR_CONTENTCLASS;
} }
// Add attachments // Add attachments
@ -746,7 +756,7 @@ class MAPIProvider {
//TODO contentclass and nativebodytype and internetcpid //TODO contentclass and nativebodytype and internetcpid
if (!isset($message->internetcpid)) $message->internetcpid = (defined('STORE_INTERNET_CPID')) ? constant('STORE_INTERNET_CPID') : INTERNET_CPID_WINDOWS1252; if (!isset($message->internetcpid)) $message->internetcpid = (defined('STORE_INTERNET_CPID')) ? constant('STORE_INTERNET_CPID') : INTERNET_CPID_WINDOWS1252;
$this->setFlag($mapimessage, $message); $this->setFlag($mapimessage, $message);
$message->contentclass = DEFAULT_EMAIL_CONTENTCLASS; if (!isset($message->contentclass)) $message->contentclass = DEFAULT_EMAIL_CONTENTCLASS;
if (!isset($message->nativebodytype)) $message->nativebodytype = $this->getNativeBodyType($messageprops); if (!isset($message->nativebodytype)) $message->nativebodytype = $this->getNativeBodyType($messageprops);
// reply, reply to all, forward flags // reply, reply to all, forward flags
@ -1842,27 +1852,6 @@ class MAPIProvider {
return $tz; return $tz;
} }
/**
* Pack timezone info for Sync
*
* @param array $tz
*
* @access private
* @return string
*/
private function getSyncBlobFromTZ($tz) {
// set the correct TZ name (done using the Bias)
if (!isset($tz["tzname"]) || !$tz["tzname"] || !isset($tz["tznamedst"]) || !$tz["tznamedst"])
$tz = TimezoneUtil::FillTZNames($tz);
$packed = pack("la64vvvvvvvv" . "la64vvvvvvvv" . "l",
$tz["bias"], $tz["tzname"], 0, $tz["dstendmonth"], $tz["dstendday"], $tz["dstendweek"], $tz["dstendhour"], $tz["dstendminute"], $tz["dstendsecond"], $tz["dstendmillis"],
$tz["stdbias"], $tz["tznamedst"], 0, $tz["dststartmonth"], $tz["dststartday"], $tz["dststartweek"], $tz["dststarthour"], $tz["dststartminute"], $tz["dststartsecond"], $tz["dststartmillis"],
$tz["dstbias"]);
return $packed;
}
/** /**
* Pack timezone info for MAPI * Pack timezone info for MAPI
* *

View file

@ -91,6 +91,9 @@ class BackendZarafa implements IBackend, ISearchProvider {
private $wastebasket; private $wastebasket;
private $addressbook; private $addressbook;
// ZCP config parameter for PR_EC_ENABLED_FEATURES / PR_EC_DISABLED_FEATURES
const ZPUSH_ENABLED = 'mobile';
/** /**
* Constructor of the Zarafa Backend * Constructor of the Zarafa Backend
* *
@ -200,6 +203,8 @@ class BackendZarafa implements IBackend, ISearchProvider {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZarafaBackend->Logon(): User '%s' is authenticated",$user)); ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZarafaBackend->Logon(): User '%s' is authenticated",$user));
$this->isZPushEnabled();
// check if this is a Zarafa 7 store with unicode support // check if this is a Zarafa 7 store with unicode support
MAPIUtils::IsUnicodeStore($this->store); MAPIUtils::IsUnicodeStore($this->store);
return true; return true;
@ -1180,6 +1185,22 @@ class BackendZarafa implements IBackend, ISearchProvider {
} }
} }
/**
* Returns the email address and the display name of the user. Used by autodiscover.
*
* @param string $username The username
*
* @access public
* @return Array
*/
public function GetUserDetails($username) {
ZLog::Write(LOGLEVEL_WBXML, sprintf("ZarafaBackend->GetUserDetails for '%s'.", $username));
$zarafauserinfo = @mapi_zarafa_getuser_by_name($this->defaultstore, $username);
$userDetails['emailaddress'] = (isset($zarafauserinfo['emailaddress']) && $zarafauserinfo['emailaddress']) ? $zarafauserinfo['emailaddress'] : false;
$userDetails['fullname'] = (isset($zarafauserinfo['fullname']) && $zarafauserinfo['fullname']) ? $zarafauserinfo['fullname'] : false;
return $userDetails;
}
/**---------------------------------------------------------------------------------------------------------- /**----------------------------------------------------------------------------------------------------------
* Private methods * Private methods
@ -1549,8 +1570,11 @@ class BackendZarafa implements IBackend, ISearchProvider {
$searchGreater = strtotime($cpo->GetSearchValueGreater()); $searchGreater = strtotime($cpo->GetSearchValueGreater());
$searchLess = strtotime($cpo->GetSearchValueLess()); $searchLess = strtotime($cpo->GetSearchValueLess());
if (version_compare(phpversion(),'5.3.4') < 0) {
ZLog::Write(LOGLEVEL_WARN, sprintf("Your system's PHP version (%s) might not correctly process unicode strings. Search containing such characters might not return correct results. It is recommended to update to at least PHP 5.3.4. See ZP-541 for more information.", phpversion()));
}
// split the search on whitespache and look for every word // split the search on whitespache and look for every word
$searchText = preg_split("/\W+/", $searchText); $searchText = preg_split("/\W+/u", $searchText);
$searchProps = array(PR_BODY, PR_SUBJECT, PR_DISPLAY_TO, PR_DISPLAY_CC, PR_SENDER_NAME, PR_SENDER_EMAIL_ADDRESS, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS); $searchProps = array(PR_BODY, PR_SUBJECT, PR_DISPLAY_TO, PR_DISPLAY_CC, PR_SENDER_NAME, PR_SENDER_EMAIL_ADDRESS, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS);
$resAnd = array(); $resAnd = array();
foreach($searchText as $term) { foreach($searchText as $term) {
@ -1831,6 +1855,25 @@ class BackendZarafa implements IBackend, ISearchProvider {
} }
return $this->addressbook; return $this->addressbook;
} }
/**
* Checks if the user is not disabled for Z-Push.
*
* @access private
* @throws FatalException if user is disabled for Z-Push
*
* @return boolean
*/
private function isZPushEnabled() {
$addressbook = $this->getAddressbook();
$userEntryid = mapi_getprops($this->store, array(PR_MAILBOX_OWNER_ENTRYID));
$mailuser = mapi_ab_openentry($addressbook, $userEntryid[PR_MAILBOX_OWNER_ENTRYID]);
$enabledFeatures = mapi_getprops($mailuser, array(PR_EC_DISABLED_FEATURES));
if (isset($enabledFeatures[PR_EC_DISABLED_FEATURES]) && is_array($enabledFeatures[PR_EC_DISABLED_FEATURES]) && in_array(self::ZPUSH_ENABLED, $enabledFeatures[PR_EC_DISABLED_FEATURES])) {
throw new FatalException("User is disabled for Z-Push.");
}
return true;
}
} }
/** /**

View file

@ -60,12 +60,68 @@
// This setting specifies the owner parameter in the certificate to look at. // This setting specifies the owner parameter in the certificate to look at.
define("CERTIFICATE_OWNER_PARAMETER", "SSL_CLIENT_S_DN_CN"); define("CERTIFICATE_OWNER_PARAMETER", "SSL_CLIENT_S_DN_CN");
/*
* Whether to use the complete email address as a login name
* (e.g. user@company.com) or the username only (user).
* This is required for Z-Push to work properly after autodiscover.
* Possible values:
* false - use the username only (default).
* true - use the complete email address.
*/
define('USE_FULLEMAIL_FOR_LOGIN', false);
/**********************************************************************************
* Device pre-authorization. Useful when using Z-Push as a standalone product.
*
* It will use the STATE_MACHINE specified below, to store the users/devices
* FILE => STATE_DIR/PreAuthUserDevices
* SQL => auth_users
*
* FALSE => default
* TRUE
*/
define('PRE_AUTHORIZE_USERS', false);
// New users are pre-authorized automatically
define('PRE_AUTHORIZE_NEW_USERS', false);
// New devices are pre-authorized automatically for pre-authorized users
define('PRE_AUTHORIZE_NEW_DEVICES', false);
// Max number of devices pre-authorized for user, you can pre-authorize more manually
define('PRE_AUTHORIZE_MAX_DEVICES', 5);
/**********************************************************************************
* Select StateMachine mechanism
*
* FILE => FileStateMachine, default
* SQL => SqlStateMachine
*/
define('STATE_MACHINE', 'FILE');
/********************************************************************************** /**********************************************************************************
* Default FileStateMachine settings * Default FileStateMachine settings
*/ */
define('STATE_DIR', '/var/lib/z-push/'); define('STATE_DIR', '/var/lib/z-push/');
/**********************************************************************************
* Optional SqlStateMachine settings
*
* DSN: formatted PDO connection string
* mysql:host=xxx;port=xxx;dbname=xxx
* DON'T FORGET TO INSTALL THE PHP-DRIVER PACKAGE!!!
* USER: username to DB
* PASSWORD: password to DB
* OPTIONS: array with options needed
*/
define('STATE_SQL_DSN', '');
define('STATE_SQL_USER', '');
define('STATE_SQL_PASSWORD', '');
define('STATE_SQL_OPTIONS', serialize(array(PDO::ATTR_PERSISTENT => true)));
/********************************************************************************** /**********************************************************************************
* Logging settings * Logging settings
* Possible LOGLEVEL and LOGUSERLEVEL values are: * Possible LOGLEVEL and LOGUSERLEVEL values are:
@ -192,6 +248,13 @@
// this full list, so this feature is disabled by default. Enable with care. // this full list, so this feature is disabled by default. Enable with care.
define('ALLOW_WEBSERVICE_USERS_ACCESS', false); define('ALLOW_WEBSERVICE_USERS_ACCESS', false);
// Users with many folders can use the 'partial foldersync' feature, where the server
// actively stops processing the folder list if it takes too long. Other requests are
// then redirected to the FolderSync to synchronize the remaining items.
// Device compatibility for this procedure is not fully understood.
// NOTE: THIS IS AN EXPERIMENTAL FEATURE WHICH COULD PREVENT YOUR MOBILES FROM SYNCHRONIZING.
define('USE_PARTIAL_FOLDERSYNC', false);
/********************************************************************************** /**********************************************************************************
* Backend settings * Backend settings
*/ */

150
sources/include/Auth/SASL.php Executable file
View file

@ -0,0 +1,150 @@
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Richard Heyes |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | o Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | o Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote |
// | products derived from this software without specific prior written |
// | permission. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// | |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@php.net> |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* Client implementation of various SASL mechanisms
*
* @author Richard Heyes <richard@php.net>
* @access public
* @version 1.0
* @package Auth_SASL
*/
/**
* Z-Push changes
*
* removed PEAR dependency by implementing own raiseError()
*
* Reference implementation used:
* http://download.pear.php.net/package/Auth_SASL-1.0.6.tgz
*
*
*/
//require_once('PEAR.php');
class Auth_SASL
{
/**
* Factory class. Returns an object of the request
* type.
*
* @param string $type One of: Anonymous
* Plain
* CramMD5
* DigestMD5
* SCRAM-* (any mechanism of the SCRAM family)
* Types are not case sensitive
*/
function &factory($type)
{
switch (strtolower($type)) {
case 'anonymous':
$filename = 'include/Auth/SASL/Anonymous.php';
$classname = 'Auth_SASL_Anonymous';
break;
case 'login':
$filename = 'include/Auth/SASL/Login.php';
$classname = 'Auth_SASL_Login';
break;
case 'plain':
$filename = 'include/Auth/SASL/Plain.php';
$classname = 'Auth_SASL_Plain';
break;
case 'external':
$filename = 'include/Auth/SASL/External.php';
$classname = 'Auth_SASL_External';
break;
case 'crammd5':
// $msg = 'Deprecated mechanism name. Use IANA-registered name: CRAM-MD5.';
// trigger_error($msg, E_USER_DEPRECATED);
case 'cram-md5':
$filename = 'include/Auth/SASL/CramMD5.php';
$classname = 'Auth_SASL_CramMD5';
break;
case 'digestmd5':
// $msg = 'Deprecated mechanism name. Use IANA-registered name: DIGEST-MD5.';
// trigger_error($msg, E_USER_DEPRECATED);
case 'digest-md5':
// $msg = 'DIGEST-MD5 is a deprecated SASL mechanism as per RFC-6331. Using it could be a security risk.';
// trigger_error($msg, E_USER_NOTICE);
$filename = 'include/Auth/SASL/DigestMD5.php';
$classname = 'Auth_SASL_DigestMD5';
break;
default:
$scram = '/^SCRAM-(.{1,9})$/i';
if (preg_match($scram, $type, $matches))
{
$hash = $matches[1];
$filename = 'include/Auth/SASL/SCRAM.php';
$classname = 'Auth_SASL_SCRAM';
$parameter = $hash;
break;
}
return Auth_SASL::raiseError('Invalid SASL mechanism type');
break;
}
require_once($filename);
if (isset($parameter))
$obj = new $classname($parameter);
else
$obj = new $classname();
return $obj;
}
/**
* Z-Push helper for error logging
* removing PEAR dependency
*
* @param string debug message
* @return boolean always false as there was an error
* @access private
*/
static function raiseError($message) {
ZLog::Write(LOGLEVEL_ERROR, "Auth_SASL error: ". $message);
return false;
}
}
?>

View file

@ -0,0 +1,71 @@
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Richard Heyes |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | o Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | o Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote |
// | products derived from this software without specific prior written |
// | permission. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// | |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@php.net> |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* Implmentation of ANONYMOUS SASL mechanism
*
* @author Richard Heyes <richard@php.net>
* @access public
* @version 1.0
* @package Auth_SASL
*/
require_once('include/Auth/SASL/Common.php');
class Auth_SASL_Anonymous extends Auth_SASL_Common
{
/**
* Not much to do here except return the token supplied.
* No encoding, hashing or encryption takes place for this
* mechanism, simply one of:
* o An email address
* o An opaque string not containing "@" that can be interpreted
* by the sysadmin
* o Nothing
*
* We could have some logic here for the second option, but this
* would by no means create something interpretable.
*
* @param string $token Optional email address or string to provide
* as trace information.
* @return string The unaltered input token
*/
function getResponse($token = '')
{
return $token;
}
}
?>

View file

@ -0,0 +1,129 @@
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Richard Heyes |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | o Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | o Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote |
// | products derived from this software without specific prior written |
// | permission. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// | |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@php.net> |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* Common functionality to SASL mechanisms
*
* @author Richard Heyes <richard@php.net>
* @access public
* @version 1.0
* @package Auth_SASL
*/
/**
* Z-Push changes
*
* removed PEAR dependency by implementing own raiseError()
*
* Reference implementation used:
* http://download.pear.php.net/package/Auth_SASL-1.0.6.tgz
*
*
*/
class Auth_SASL_Common
{
/**
* Function which implements HMAC MD5 digest
*
* @param string $key The secret key
* @param string $data The data to hash
* @param bool $raw_output Whether the digest is returned in binary or hexadecimal format.
*
* @return string The HMAC-MD5 digest
*/
function _HMAC_MD5($key, $data, $raw_output = FALSE)
{
if (strlen($key) > 64) {
$key = pack('H32', md5($key));
}
if (strlen($key) < 64) {
$key = str_pad($key, 64, chr(0));
}
$k_ipad = substr($key, 0, 64) ^ str_repeat(chr(0x36), 64);
$k_opad = substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64);
$inner = pack('H32', md5($k_ipad . $data));
$digest = md5($k_opad . $inner, $raw_output);
return $digest;
}
/**
* Function which implements HMAC-SHA-1 digest
*
* @param string $key The secret key
* @param string $data The data to hash
* @param bool $raw_output Whether the digest is returned in binary or hexadecimal format.
* @return string The HMAC-SHA-1 digest
* @author Jehan <jehan.marmottard@gmail.com>
* @access protected
*/
protected function _HMAC_SHA1($key, $data, $raw_output = FALSE)
{
if (strlen($key) > 64) {
$key = sha1($key, TRUE);
}
if (strlen($key) < 64) {
$key = str_pad($key, 64, chr(0));
}
$k_ipad = substr($key, 0, 64) ^ str_repeat(chr(0x36), 64);
$k_opad = substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64);
$inner = pack('H40', sha1($k_ipad . $data));
$digest = sha1($k_opad . $inner, $raw_output);
return $digest;
}
/**
* Z-Push helper for error logging
* removing PEAR dependency
*
* @param string debug message
* @return boolean always false as there was an error
* @access private
*/
function raiseError($message) {
ZLog::Write(LOGLEVEL_ERROR, "SCRAM error: ". $message);
return false;
}
}
?>

View file

@ -0,0 +1,68 @@
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Richard Heyes |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | o Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | o Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote |
// | products derived from this software without specific prior written |
// | permission. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// | |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@php.net> |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* Implmentation of CRAM-MD5 SASL mechanism
*
* @author Richard Heyes <richard@php.net>
* @access public
* @version 1.0
* @package Auth_SASL
*/
require_once('include/Auth/SASL/Common.php');
class Auth_SASL_CramMD5 extends Auth_SASL_Common
{
/**
* Implements the CRAM-MD5 SASL mechanism
* This DOES NOT base64 encode the return value,
* you will need to do that yourself.
*
* @param string $user Username
* @param string $pass Password
* @param string $challenge The challenge supplied by the server.
* this should be already base64_decoded.
*
* @return string The string to pass back to the server, of the form
* "<user> <digest>". This is NOT base64_encoded.
*/
function getResponse($user, $pass, $challenge)
{
return $user . ' ' . $this->_HMAC_MD5($pass, $challenge);
}
}
?>

View file

@ -0,0 +1,197 @@
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Richard Heyes |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | o Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | o Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote |
// | products derived from this software without specific prior written |
// | permission. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// | |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@php.net> |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* Implmentation of DIGEST-MD5 SASL mechanism
*
* @author Richard Heyes <richard@php.net>
* @access public
* @version 1.0
* @package Auth_SASL
*/
require_once('include/Auth/SASL/Common.php');
class Auth_SASL_DigestMD5 extends Auth_SASL_Common
{
/**
* Provides the (main) client response for DIGEST-MD5
* requires a few extra parameters than the other
* mechanisms, which are unavoidable.
*
* @param string $authcid Authentication id (username)
* @param string $pass Password
* @param string $challenge The digest challenge sent by the server
* @param string $hostname The hostname of the machine you're connecting to
* @param string $service The servicename (eg. imap, pop, acap etc)
* @param string $authzid Authorization id (username to proxy as)
* @return string The digest response (NOT base64 encoded)
* @access public
*/
function getResponse($authcid, $pass, $challenge, $hostname, $service, $authzid = '')
{
$challenge = $this->_parseChallenge($challenge);
$authzid_string = '';
if ($authzid != '') {
$authzid_string = ',authzid="' . $authzid . '"';
}
if (!empty($challenge)) {
$cnonce = $this->_getCnonce();
$digest_uri = sprintf('%s/%s', $service, $hostname);
$response_value = $this->_getResponseValue($authcid, $pass, $challenge['realm'], $challenge['nonce'], $cnonce, $digest_uri, $authzid);
if ($challenge['realm']) {
return sprintf('username="%s",realm="%s"' . $authzid_string .
',nonce="%s",cnonce="%s",nc=00000001,qop=auth,digest-uri="%s",response=%s,maxbuf=%d', $authcid, $challenge['realm'], $challenge['nonce'], $cnonce, $digest_uri, $response_value, $challenge['maxbuf']);
} else {
return sprintf('username="%s"' . $authzid_string . ',nonce="%s",cnonce="%s",nc=00000001,qop=auth,digest-uri="%s",response=%s,maxbuf=%d', $authcid, $challenge['nonce'], $cnonce, $digest_uri, $response_value, $challenge['maxbuf']);
}
} else {
return $this->raiseError('Invalid digest challenge');
}
}
/**
* Parses and verifies the digest challenge*
*
* @param string $challenge The digest challenge
* @return array The parsed challenge as an assoc
* array in the form "directive => value".
* @access private
*/
function _parseChallenge($challenge)
{
$tokens = array();
while (preg_match('/^([a-z-]+)=("[^"]+(?<!\\\)"|[^,]+)/i', $challenge, $matches)) {
// Ignore these as per rfc2831
if ($matches[1] == 'opaque' OR $matches[1] == 'domain') {
$challenge = substr($challenge, strlen($matches[0]) + 1);
continue;
}
// Allowed multiple "realm" and "auth-param"
if (!empty($tokens[$matches[1]]) AND ($matches[1] == 'realm' OR $matches[1] == 'auth-param')) {
if (is_array($tokens[$matches[1]])) {
$tokens[$matches[1]][] = preg_replace('/^"(.*)"$/', '\\1', $matches[2]);
} else {
$tokens[$matches[1]] = array($tokens[$matches[1]], preg_replace('/^"(.*)"$/', '\\1', $matches[2]));
}
// Any other multiple instance = failure
} elseif (!empty($tokens[$matches[1]])) {
$tokens = array();
break;
} else {
$tokens[$matches[1]] = preg_replace('/^"(.*)"$/', '\\1', $matches[2]);
}
// Remove the just parsed directive from the challenge
$challenge = substr($challenge, strlen($matches[0]) + 1);
}
/**
* Defaults and required directives
*/
// Realm
if (empty($tokens['realm'])) {
$tokens['realm'] = "";
}
// Maxbuf
if (empty($tokens['maxbuf'])) {
$tokens['maxbuf'] = 65536;
}
// Required: nonce, algorithm
if (empty($tokens['nonce']) OR empty($tokens['algorithm'])) {
return array();
}
return $tokens;
}
/**
* Creates the response= part of the digest response
*
* @param string $authcid Authentication id (username)
* @param string $pass Password
* @param string $realm Realm as provided by the server
* @param string $nonce Nonce as provided by the server
* @param string $cnonce Client nonce
* @param string $digest_uri The digest-uri= value part of the response
* @param string $authzid Authorization id
* @return string The response= part of the digest response
* @access private
*/
function _getResponseValue($authcid, $pass, $realm, $nonce, $cnonce, $digest_uri, $authzid = '')
{
if ($authzid == '') {
$A1 = sprintf('%s:%s:%s', pack('H32', md5(sprintf('%s:%s:%s', $authcid, $realm, $pass))), $nonce, $cnonce);
} else {
$A1 = sprintf('%s:%s:%s:%s', pack('H32', md5(sprintf('%s:%s:%s', $authcid, $realm, $pass))), $nonce, $cnonce, $authzid);
}
$A2 = 'AUTHENTICATE:' . $digest_uri;
return md5(sprintf('%s:%s:00000001:%s:auth:%s', md5($A1), $nonce, $cnonce, md5($A2)));
}
/**
* Creates the client nonce for the response
*
* @return string The cnonce value
* @access private
*/
function _getCnonce()
{
if (@file_exists('/dev/urandom') && $fd = @fopen('/dev/urandom', 'r')) {
return base64_encode(fread($fd, 32));
} elseif (@file_exists('/dev/random') && $fd = @fopen('/dev/random', 'r')) {
return base64_encode(fread($fd, 32));
} else {
$str = '';
for ($i=0; $i<32; $i++) {
$str .= chr(mt_rand(0, 255));
}
return base64_encode($str);
}
}
}
?>

View file

@ -0,0 +1,63 @@
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2008 Christoph Schulz |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | o Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | o Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote |
// | products derived from this software without specific prior written |
// | permission. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// | |
// +-----------------------------------------------------------------------+
// | Author: Christoph Schulz <develop@kristov.de> |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* Implmentation of EXTERNAL SASL mechanism
*
* @author Christoph Schulz <develop@kristov.de>
* @access public
* @version 1.0.3
* @package Auth_SASL
*/
require_once('include/Auth/SASL/Common.php');
class Auth_SASL_External extends Auth_SASL_Common
{
/**
* Returns EXTERNAL response
*
* @param string $authcid Authentication id (username)
* @param string $pass Password
* @param string $authzid Autorization id
* @return string EXTERNAL Response
*/
function getResponse($authcid, $pass, $authzid = '')
{
return $authzid;
}
}
?>

View file

@ -0,0 +1,65 @@
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Richard Heyes |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | o Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | o Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote |
// | products derived from this software without specific prior written |
// | permission. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// | |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@php.net> |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* This is technically not a SASL mechanism, however
* it's used by Net_Sieve, Net_Cyrus and potentially
* other protocols , so here is a good place to abstract
* it.
*
* @author Richard Heyes <richard@php.net>
* @access public
* @version 1.0
* @package Auth_SASL
*/
require_once('include/Auth/SASL/Common.php');
class Auth_SASL_Login extends Auth_SASL_Common
{
/**
* Pseudo SASL LOGIN mechanism
*
* @param string $user Username
* @param string $pass Password
* @return string LOGIN string
*/
function getResponse($user, $pass)
{
return sprintf('LOGIN %s %s', $user, $pass);
}
}
?>

View file

@ -0,0 +1,63 @@
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Richard Heyes |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | o Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | o Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote |
// | products derived from this software without specific prior written |
// | permission. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// | |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@php.net> |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* Implmentation of PLAIN SASL mechanism
*
* @author Richard Heyes <richard@php.net>
* @access public
* @version 1.0
* @package Auth_SASL
*/
require_once('include/Auth/SASL/Common.php');
class Auth_SASL_Plain extends Auth_SASL_Common
{
/**
* Returns PLAIN response
*
* @param string $authcid Authentication id (username)
* @param string $pass Password
* @param string $authzid Autorization id
* @return string PLAIN Response
*/
function getResponse($authcid, $pass, $authzid = '')
{
return $authzid . chr(0) . $authcid . chr(0) . $pass;
}
}
?>

View file

@ -0,0 +1,305 @@
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2011 Jehan |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | o Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | o Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote |
// | products derived from this software without specific prior written |
// | permission. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// | |
// +-----------------------------------------------------------------------+
// | Author: Jehan <jehan.marmottard@gmail.com |
// +-----------------------------------------------------------------------+
//
// $Id$
/**
* Implementation of SCRAM-* SASL mechanisms.
* SCRAM mechanisms have 3 main steps (initial response, response to the server challenge, then server signature
* verification) which keep state-awareness. Therefore a single class instanciation must be done and reused for the whole
* authentication process.
*
* @author Jehan <jehan.marmottard@gmail.com>
* @access public
* @version 1.0
* @package Auth_SASL
*/
require_once('include/Auth/SASL/Common.php');
class Auth_SASL_SCRAM extends Auth_SASL_Common
{
/**
* Construct a SCRAM-H client where 'H' is a cryptographic hash function.
*
* @param string $hash The name cryptographic hash function 'H' as registered by IANA in the "Hash Function Textual
* Names" registry.
* @link http://www.iana.org/assignments/hash-function-text-names/hash-function-text-names.xml "Hash Function Textual
* Names"
* format of core PHP hash function.
* @access public
*/
function __construct($hash)
{
// Though I could be strict, I will actually also accept the naming used in the PHP core hash framework.
// For instance "sha1" is accepted, while the registered hash name should be "SHA-1".
$hash = strtolower($hash);
$hashes = array('md2' => 'md2',
'md5' => 'md5',
'sha-1' => 'sha1',
'sha1' => 'sha1',
'sha-224' > 'sha224',
'sha224' > 'sha224',
'sha-256' => 'sha256',
'sha256' => 'sha256',
'sha-384' => 'sha384',
'sha384' => 'sha384',
'sha-512' => 'sha512',
'sha512' => 'sha512');
if (function_exists('hash_hmac') && isset($hashes[$hash]))
{
$this->hash = create_function('$data', 'return hash("' . $hashes[$hash] . '", $data, TRUE);');
$this->hmac = create_function('$key,$str,$raw', 'return hash_hmac("' . $hashes[$hash] . '", $str, $key, $raw);');
}
elseif ($hash == 'md5')
{
$this->hash = create_function('$data', 'return md5($data, true);');
$this->hmac = array($this, '_HMAC_MD5');
}
elseif (in_array($hash, array('sha1', 'sha-1')))
{
$this->hash = create_function('$data', 'return sha1($data, true);');
$this->hmac = array($this, '_HMAC_SHA1');
}
else
return $this->raiseError('Invalid SASL mechanism type');
}
/**
* Provides the (main) client response for SCRAM-H.
*
* @param string $authcid Authentication id (username)
* @param string $pass Password
* @param string $challenge The challenge sent by the server.
* If the challenge is NULL or an empty string, the result will be the "initial response".
* @param string $authzid Authorization id (username to proxy as)
* @return string|false The response (binary, NOT base64 encoded)
* @access public
*/
public function getResponse($authcid, $pass, $challenge = NULL, $authzid = NULL)
{
$authcid = $this->_formatName($authcid);
if (empty($authcid))
{
return false;
}
if (!empty($authzid))
{
$authzid = $this->_formatName($authzid);
if (empty($authzid))
{
return false;
}
}
if (empty($challenge))
{
return $this->_generateInitialResponse($authcid, $authzid);
}
else
{
return $this->_generateResponse($challenge, $pass);
}
}
/**
* Prepare a name for inclusion in a SCRAM response.
*
* @param string $username a name to be prepared.
* @return string the reformated name.
* @access private
*/
private function _formatName($username)
{
// TODO: prepare through the SASLprep profile of the stringprep algorithm.
// See RFC-4013.
$username = str_replace('=', '=3D', $username);
$username = str_replace(',', '=2C', $username);
return $username;
}
/**
* Generate the initial response which can be either sent directly in the first message or as a response to an empty
* server challenge.
*
* @param string $authcid Prepared authentication identity.
* @param string $authzid Prepared authorization identity.
* @return string The SCRAM response to send.
* @access private
*/
private function _generateInitialResponse($authcid, $authzid)
{
$init_rep = '';
$gs2_cbind_flag = 'n,'; // TODO: support channel binding.
$this->gs2_header = $gs2_cbind_flag . (!empty($authzid)? 'a=' . $authzid : '') . ',';
// I must generate a client nonce and "save" it for later comparison on second response.
$this->cnonce = $this->_getCnonce();
// XXX: in the future, when mandatory and/or optional extensions are defined in any updated RFC,
// this message can be updated.
$this->first_message_bare = 'n=' . $authcid . ',r=' . $this->cnonce;
return $this->gs2_header . $this->first_message_bare;
}
/**
* Parses and verifies a non-empty SCRAM challenge.
*
* @param string $challenge The SCRAM challenge
* @return string|false The response to send; false in case of wrong challenge or if an initial response has not
* been generated first.
* @access private
*/
private function _generateResponse($challenge, $password)
{
// XXX: as I don't support mandatory extension, I would fail on them.
// And I simply ignore any optional extension.
$server_message_regexp = "#^r=([\x21-\x2B\x2D-\x7E]+),s=((?:[A-Za-z0-9/+]{4})*(?:[A-Za-z0-9]{3}=|[A-Xa-z0-9]{2}==)?),i=([0-9]*)(,[A-Za-z]=[^,])*$#";
if (!isset($this->cnonce, $this->gs2_header)
|| !preg_match($server_message_regexp, $challenge, $matches))
{
return false;
}
$nonce = $matches[1];
$salt = base64_decode($matches[2]);
if (!$salt)
{
// Invalid Base64.
return false;
}
$i = intval($matches[3]);
$cnonce = substr($nonce, 0, strlen($this->cnonce));
if ($cnonce <> $this->cnonce)
{
// Invalid challenge! Are we under attack?
return false;
}
$channel_binding = 'c=' . base64_encode($this->gs2_header); // TODO: support channel binding.
$final_message = $channel_binding . ',r=' . $nonce; // XXX: no extension.
// TODO: $password = $this->normalize($password); // SASLprep profile of stringprep.
$saltedPassword = $this->hi($password, $salt, $i);
$this->saltedPassword = $saltedPassword;
$clientKey = call_user_func($this->hmac, $saltedPassword, "Client Key", TRUE);
$storedKey = call_user_func($this->hash, $clientKey, TRUE);
$authMessage = $this->first_message_bare . ',' . $challenge . ',' . $final_message;
$this->authMessage = $authMessage;
$clientSignature = call_user_func($this->hmac, $storedKey, $authMessage, TRUE);
$clientProof = $clientKey ^ $clientSignature;
$proof = ',p=' . base64_encode($clientProof);
return $final_message . $proof;
}
/**
* SCRAM has also a server verification step. On a successful outcome, it will send additional data which must
* absolutely be checked against this function. If this fails, the entity which we are communicating with is probably
* not the server as it has not access to your ServerKey.
*
* @param string $data The additional data sent along a successful outcome.
* @return bool Whether the server has been authenticated.
* If false, the client must close the connection and consider to be under a MITM attack.
* @access public
*/
public function processOutcome($data)
{
$verifier_regexp = '#^v=((?:[A-Za-z0-9/+]{4})*(?:[A-Za-z0-9]{3}=|[A-Xa-z0-9]{2}==)?)$#';
if (!isset($this->saltedPassword, $this->authMessage)
|| !preg_match($verifier_regexp, $data, $matches))
{
// This cannot be an outcome, you never sent the challenge's response.
return false;
}
$verifier = $matches[1];
$proposed_serverSignature = base64_decode($verifier);
$serverKey = call_user_func($this->hmac, $this->saltedPassword, "Server Key", true);
$serverSignature = call_user_func($this->hmac, $serverKey, $this->authMessage, TRUE);
return ($proposed_serverSignature === $serverSignature);
}
/**
* Hi() call, which is essentially PBKDF2 (RFC-2898) with HMAC-H() as the pseudorandom function.
*
* @param string $str The string to hash.
* @param string $hash The hash value.
* @param int $i The iteration count.
* @access private
*/
private function hi($str, $salt, $i)
{
$int1 = "\0\0\0\1";
$ui = call_user_func($this->hmac, $str, $salt . $int1, true);
$result = $ui;
for ($k = 1; $k < $i; $k++)
{
$ui = call_user_func($this->hmac, $str, $ui, true);
$result = $result ^ $ui;
}
return $result;
}
/**
* Creates the client nonce for the response
*
* @return string The cnonce value
* @access private
* @author Richard Heyes <richard@php.net>
*/
private function _getCnonce()
{
// TODO: I reused the nonce function from the DigestMD5 class.
// I should probably make this a protected function in Common.
if (@file_exists('/dev/urandom') && $fd = @fopen('/dev/urandom', 'r')) {
return base64_encode(fread($fd, 32));
} elseif (@file_exists('/dev/random') && $fd = @fopen('/dev/random', 'r')) {
return base64_encode(fread($fd, 32));
} else {
$str = '';
for ($i=0; $i<32; $i++) {
$str .= chr(mt_rand(0, 255));
}
return base64_encode($str);
}
}
}
?>

300
sources/include/Mail.php Normal file
View file

@ -0,0 +1,300 @@
<?php
/**
* PEAR's Mail:: interface.
*
* PHP versions 4 and 5
*
* LICENSE:
*
* Copyright (c) 2002-2007, Richard Heyes
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* o Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* o Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* o The names of the authors may not be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @category Mail
* @package Mail
* @author Chuck Hagenbuch <chuck@horde.org>
* @copyright 1997-2010 Chuck Hagenbuch
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version CVS: $Id: Mail.php 307489 2011-01-14 19:06:57Z alec $
* @link http://pear.php.net/package/Mail/
*/
/**
* Z-Push changes
*
* removed PEAR dependency by implementing own raiseError()
*
* Reference implementation used:
* http://download.pear.php.net/package/Mail-1.2.0.tgz
* SVN trunk version r333509
*
*
*/
/**
* PEAR's Mail:: interface. Defines the interface for implementing
* mailers under the PEAR hierarchy, and provides supporting functions
* useful in multiple mailer backends.
*
* @access public
* @version $Revision: 307489 $
* @package Mail
*/
class Mail
{
/**
* Line terminator used for separating header lines.
* @var string
*/
var $sep = "\r\n";
/**
* Provides an interface for generating Mail:: objects of various
* types
*
* @param string $driver The kind of Mail:: object to instantiate.
* @param array $params The parameters to pass to the Mail:: object.
* @return object Mail a instance of the driver class or if fails a PEAR Error
* @access public
*/
static function &factory($driver, $params = array())
{
$driver = strtolower($driver);
@include_once 'include/Mail/' . $driver . '.php';
$class = 'Mail_' . $driver;
if (class_exists($class)) {
$mailer = new $class($params);
return $mailer;
} else {
return Mail::raiseError('Unable to find class for driver ' . $driver);
}
}
/**
* Implements Mail::send() function using php's built-in mail()
* command.
*
* @param mixed $recipients Either a comma-seperated list of recipients
* (RFC822 compliant), or an array of recipients,
* each RFC822 valid. This may contain recipients not
* specified in the headers, for Bcc:, resending
* messages, etc.
*
* @param array $headers The array of headers to send with the mail, in an
* associative array, where the array key is the
* header name (ie, 'Subject'), and the array value
* is the header value (ie, 'test'). The header
* produced from those values would be 'Subject:
* test'.
*
* @param string $body The full text of the message body, including any
* Mime parts, etc.
*
* @return mixed Returns true on success, or a PEAR_Error
* containing a descriptive error message on
* failure.
*
* @access public
* @deprecated use Mail_mail::send instead
*/
function send($recipients, $headers, $body)
{
if (!is_array($headers)) {
return Mail::raiseError('$headers must be an array');
}
$result = $this->_sanitizeHeaders($headers);
//if (is_a($result, 'PEAR_Error')) {
if ($result === false) {
return $result;
}
// if we're passed an array of recipients, implode it.
if (is_array($recipients)) {
$recipients = implode(', ', $recipients);
}
// get the Subject out of the headers array so that we can
// pass it as a seperate argument to mail().
$subject = '';
if (isset($headers['Subject'])) {
$subject = $headers['Subject'];
unset($headers['Subject']);
}
// flatten the headers out.
list(, $text_headers) = Mail::prepareHeaders($headers);
return mail($recipients, $subject, $body, $text_headers);
}
/**
* Sanitize an array of mail headers by removing any additional header
* strings present in a legitimate header's value. The goal of this
* filter is to prevent mail injection attacks.
*
* @param array $headers The associative array of headers to sanitize.
*
* @access private
*/
function _sanitizeHeaders(&$headers)
{
foreach ($headers as $key => $value) {
$headers[$key] =
preg_replace('=((<CR>|<LF>|0x0A/%0A|0x0D/%0D|\\n|\\r)\S).*=i',
null, $value);
}
return true;
}
/**
* Take an array of mail headers and return a string containing
* text usable in sending a message.
*
* @param array $headers The array of headers to prepare, in an associative
* array, where the array key is the header name (ie,
* 'Subject'), and the array value is the header
* value (ie, 'test'). The header produced from those
* values would be 'Subject: test'.
*
* @return mixed Returns false if it encounters a bad address,
* otherwise returns an array containing two
* elements: Any From: address found in the headers,
* and the plain text version of the headers.
* @access private
*/
function prepareHeaders($headers)
{
$lines = array();
$from = null;
foreach ($headers as $key => $value) {
if (strcasecmp($key, 'From') === 0) {
include_once 'include/z_RFC822.php';
$parser = new Mail_RFC822();
$addresses = $parser->parseAddressList($value, 'localhost', false);
//if (is_a($addresses, 'PEAR_Error')) {
if ($addresses === false) {
return $addresses;
}
$from = $addresses[0]->mailbox . '@' . $addresses[0]->host;
// Reject envelope From: addresses with spaces.
if (strstr($from, ' ')) {
return false;
}
$lines[] = $key . ': ' . $value;
} elseif (strcasecmp($key, 'Received') === 0) {
$received = array();
if (is_array($value)) {
foreach ($value as $line) {
$received[] = $key . ': ' . $line;
}
}
else {
$received[] = $key . ': ' . $value;
}
// Put Received: headers at the top. Spam detectors often
// flag messages with Received: headers after the Subject:
// as spam.
$lines = array_merge($received, $lines);
} else {
// If $value is an array (i.e., a list of addresses), convert
// it to a comma-delimited string of its elements (addresses).
if (is_array($value)) {
$value = implode(', ', $value);
}
$lines[] = $key . ': ' . $value;
}
}
return array($from, join($this->sep, $lines));
}
/**
* Take a set of recipients and parse them, returning an array of
* bare addresses (forward paths) that can be passed to sendmail
* or an smtp server with the rcpt to: command.
*
* @param mixed Either a comma-seperated list of recipients
* (RFC822 compliant), or an array of recipients,
* each RFC822 valid.
*
* @return mixed An array of forward paths (bare addresses) or a PEAR_Error
* object if the address list could not be parsed.
* @access private
*/
function parseRecipients($recipients)
{
include_once 'include/z_RFC822.php';
// if we're passed an array, assume addresses are valid and
// implode them before parsing.
if (is_array($recipients)) {
$recipients = implode(', ', $recipients);
}
// Parse recipients, leaving out all personal info. This is
// for smtp recipients, etc. All relevant personal information
// should already be in the headers.
$parser = new Mail_RFC822();
$addresses = $parser->parseAddressList($recipients, 'localhost', false);
// If parseAddressList() returned a PEAR_Error object, just return it.
//if (is_a($addresses, 'PEAR_Error')) {
if ($addresses === false) {
return $addresses;
}
$recipients = array();
if (is_array($addresses)) {
foreach ($addresses as $ob) {
$recipients[] = $ob->mailbox . '@' . $ob->host;
}
}
return $recipients;
}
/**
* Z-Push helper for error logging
* removing PEAR dependency
*
* @param string debug message
* @return boolean always false as there was an error
* @access private
*/
static function raiseError($message) {
ZLog::Write(LOGLEVEL_ERROR, "Mail error: ". $message);
return false;
}
}

View file

@ -0,0 +1,193 @@
<?php
/**
* internal PHP-mail() implementation of the PEAR Mail:: interface.
*
* PHP versions 4 and 5
*
* LICENSE:
*
* Copyright (c) 2010 Chuck Hagenbuch
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* o Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* o Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* o The names of the authors may not be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @category Mail
* @package Mail
* @author Chuck Hagenbuch <chuck@horde.org>
* @copyright 2010 Chuck Hagenbuch
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version CVS: $Id: mail.php 294747 2010-02-08 08:18:33Z clockwerx $
* @link http://pear.php.net/package/Mail/
*/
/**
* Z-Push changes
*
* removed PEAR dependency by implementing own raiseError()
*
* Reference implementation used:
* http://download.pear.php.net/package/Mail-1.2.0.tgz
*
*
*/
/**
* internal PHP-mail() implementation of the PEAR Mail:: interface.
* @package Mail
* @version $Revision: 294747 $
*/
class Mail_mail extends Mail {
/**
* Any arguments to pass to the mail() function.
* @var string
*/
var $_params = '';
/**
* Constructor.
*
* Instantiates a new Mail_mail:: object based on the parameters
* passed in.
*
* @param array $params Extra arguments for the mail() function.
*/
function Mail_mail($params = null)
{
// The other mail implementations accept parameters as arrays.
// In the interest of being consistent, explode an array into
// a string of parameter arguments.
if (is_array($params)) {
$this->_params = join(' ', $params);
} else {
$this->_params = $params;
}
/* Because the mail() function may pass headers as command
* line arguments, we can't guarantee the use of the standard
* "\r\n" separator. Instead, we use the system's native line
* separator. */
if (defined('PHP_EOL')) {
$this->sep = PHP_EOL;
} else {
$this->sep = (strpos(PHP_OS, 'WIN') === false) ? "\n" : "\r\n";
}
}
/**
* Implements Mail_mail::send() function using php's built-in mail()
* command.
*
* @param mixed $recipients Either a comma-seperated list of recipients
* (RFC822 compliant), or an array of recipients,
* each RFC822 valid. This may contain recipients not
* specified in the headers, for Bcc:, resending
* messages, etc.
*
* @param array $headers The array of headers to send with the mail, in an
* associative array, where the array key is the
* header name (ie, 'Subject'), and the array value
* is the header value (ie, 'test'). The header
* produced from those values would be 'Subject:
* test'.
*
* @param string $body The full text of the message body, including any
* Mime parts, etc.
*
* @return mixed Returns true on success, or a PEAR_Error
* containing a descriptive error message on
* failure.
*
* @access public
*/
function send($recipients, $headers, $body)
{
if (!is_array($headers)) {
return Mail_mail::raiseError('$headers must be an array');
}
$result = $this->_sanitizeHeaders($headers);
//if (is_a($result, 'PEAR_Error')) {
if ($result === false) {
return $result;
}
// If we're passed an array of recipients, implode it.
if (is_array($recipients)) {
$recipients = implode(', ', $recipients);
}
// Get the Subject out of the headers array so that we can
// pass it as a seperate argument to mail().
$subject = '';
if (isset($headers['Subject'])) {
$subject = $headers['Subject'];
unset($headers['Subject']);
}
// Also remove the To: header. The mail() function will add its own
// To: header based on the contents of $recipients.
unset($headers['To']);
// Flatten the headers out.
$headerElements = $this->prepareHeaders($headers);
//if (is_a($headerElements, 'PEAR_Error')) {
if ($headerElements === false) {
return $headerElements;
}
list(, $text_headers) = $headerElements;
// We only use mail()'s optional fifth parameter if the additional
// parameters have been provided and we're not running in safe mode.
if (empty($this->_params) || ini_get('safe_mode')) {
$result = mail($recipients, $subject, $body, $text_headers);
} else {
$result = mail($recipients, $subject, $body, $text_headers,
$this->_params);
}
// If the mail() function returned failure, we need to create a
// PEAR_Error object and return it instead of the boolean result.
if ($result === false) {
$result = Mail_mail::raiseError('mail() returned failure');
}
return $result;
}
/**
* Z-Push helper for error logging
* removing PEAR dependency
*
* @param string debug message
* @return boolean always false as there was an error
* @access private
*/
static function raiseError($message) {
ZLog::Write(LOGLEVEL_ERROR, "Mail<mail> error: ". $message);
return false;
}
}

View file

@ -0,0 +1,197 @@
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Chuck Hagenbuch <chuck@horde.org> |
// +----------------------------------------------------------------------+
/**
* Z-Push changes
*
* removed PEAR dependency by implementing own raiseError()
*
* Reference implementation used:
* http://download.pear.php.net/package/Mail-1.2.0.tgz
*
*
*/
/**
* Sendmail implementation of the PEAR Mail:: interface.
* @access public
* @package Mail
* @version $Revision: 294744 $
*/
class Mail_sendmail extends Mail {
/**
* The location of the sendmail or sendmail wrapper binary on the
* filesystem.
* @var string
*/
var $sendmail_path = '/usr/sbin/sendmail';
/**
* Any extra command-line parameters to pass to the sendmail or
* sendmail wrapper binary.
* @var string
*/
var $sendmail_args = '-i';
/**
* Constructor.
*
* Instantiates a new Mail_sendmail:: object based on the parameters
* passed in. It looks for the following parameters:
* sendmail_path The location of the sendmail binary on the
* filesystem. Defaults to '/usr/sbin/sendmail'.
*
* sendmail_args Any extra parameters to pass to the sendmail
* or sendmail wrapper binary.
*
* If a parameter is present in the $params array, it replaces the
* default.
*
* @param array $params Hash containing any parameters different from the
* defaults.
* @access public
*/
function Mail_sendmail($params)
{
if (isset($params['sendmail_path'])) {
$this->sendmail_path = $params['sendmail_path'];
}
if (isset($params['sendmail_args'])) {
$this->sendmail_args = $params['sendmail_args'];
}
/*
* Because we need to pass message headers to the sendmail program on
* the commandline, we can't guarantee the use of the standard "\r\n"
* separator. Instead, we use the system's native line separator.
*/
if (defined('PHP_EOL')) {
$this->sep = PHP_EOL;
} else {
$this->sep = (strpos(PHP_OS, 'WIN') === false) ? "\n" : "\r\n";
}
}
/**
* Implements Mail::send() function using the sendmail
* command-line binary.
*
* @param mixed $recipients Either a comma-seperated list of recipients
* (RFC822 compliant), or an array of recipients,
* each RFC822 valid. This may contain recipients not
* specified in the headers, for Bcc:, resending
* messages, etc.
*
* @param array $headers The array of headers to send with the mail, in an
* associative array, where the array key is the
* header name (ie, 'Subject'), and the array value
* is the header value (ie, 'test'). The header
* produced from those values would be 'Subject:
* test'.
*
* @param string $body The full text of the message body, including any
* Mime parts, etc.
*
* @return mixed Returns true on success, or a PEAR_Error
* containing a descriptive error message on
* failure.
* @access public
*/
function send($recipients, $headers, $body)
{
if (!is_array($headers)) {
return Mail_sendmail::raiseError('$headers must be an array');
}
$result = $this->_sanitizeHeaders($headers);
//if (is_a($result, 'PEAR_Error')) {
if ($result === false) {
return $result;
}
$recipients = $this->parseRecipients($recipients);
//if (is_a($recipients, 'PEAR_Error')) {
if ($recipients === false) {
return $recipients;
}
$recipients = implode(' ', array_map('escapeshellarg', $recipients));
$headerElements = $this->prepareHeaders($headers);
//if (is_a($headerElements, 'PEAR_Error')) {
if ($headerElements === false) {
return $headerElements;
}
list($from, $text_headers) = $headerElements;
/* Since few MTAs are going to allow this header to be forged
* unless it's in the MAIL FROM: exchange, we'll use
* Return-Path instead of From: if it's set. */
if (!empty($headers['Return-Path'])) {
$from = $headers['Return-Path'];
}
if (!isset($from)) {
return Mail_sendmail::raiseError('No from address given.');
} elseif (strpos($from, ' ') !== false ||
strpos($from, ';') !== false ||
strpos($from, '&') !== false ||
strpos($from, '`') !== false) {
return Mail_sendmail::raiseError('From address specified with dangerous characters.');
}
$from = escapeshellarg($from); // Security bug #16200
$mail = @popen($this->sendmail_path . (!empty($this->sendmail_args) ? ' ' . $this->sendmail_args : '') . " -f$from -- $recipients", 'w');
if (!$mail) {
return Mail_sendmail::raiseError('Failed to open sendmail [' . $this->sendmail_path . '] for execution.');
}
// Write the headers following by two newlines: one to end the headers
// section and a second to separate the headers block from the body.
fputs($mail, $text_headers . $this->sep . $this->sep);
fputs($mail, $body);
$result = pclose($mail);
if (version_compare(phpversion(), '4.2.3') == -1) {
// With older php versions, we need to shift the pclose
// result to get the exit code.
$result = $result >> 8 & 0xFF;
}
if ($result != 0) {
return Mail_sendmail::raiseError('sendmail returned error code ' . $result,
$result);
}
return true;
}
/**
* Z-Push helper for error logging
* removing PEAR dependency
*
* @param string debug message
* @return boolean always false as there was an error
* @access private
*/
static function raiseError($message) {
ZLog::Write(LOGLEVEL_ERROR, "Mail<sendmail> error: ". $message);
return false;
}
}

View file

@ -0,0 +1,490 @@
<?php
/**
* SMTP implementation of the PEAR Mail interface. Requires the Net_SMTP class.
*
* PHP versions 4 and 5
*
* LICENSE:
*
* Copyright (c) 2010, Chuck Hagenbuch
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* o Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* o Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* o The names of the authors may not be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @category HTTP
* @package HTTP_Request
* @author Jon Parise <jon@php.net>
* @author Chuck Hagenbuch <chuck@horde.org>
* @copyright 2010 Chuck Hagenbuch
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version CVS: $Id: smtp.php 307488 2011-01-14 19:00:54Z alec $
* @link http://pear.php.net/package/Mail/
*/
/**
* Z-Push changes
*
* removed PEAR dependency by implementing own raiseError()
*
* Reference implementation used:
* http://download.pear.php.net/package/Mail-1.2.0.tgz
*
*
*/
/** Error: Failed to create a Net_SMTP object */
define('PEAR_MAIL_SMTP_ERROR_CREATE', 10000);
/** Error: Failed to connect to SMTP server */
define('PEAR_MAIL_SMTP_ERROR_CONNECT', 10001);
/** Error: SMTP authentication failure */
define('PEAR_MAIL_SMTP_ERROR_AUTH', 10002);
/** Error: No From: address has been provided */
define('PEAR_MAIL_SMTP_ERROR_FROM', 10003);
/** Error: Failed to set sender */
define('PEAR_MAIL_SMTP_ERROR_SENDER', 10004);
/** Error: Failed to add recipient */
define('PEAR_MAIL_SMTP_ERROR_RECIPIENT', 10005);
/** Error: Failed to send data */
define('PEAR_MAIL_SMTP_ERROR_DATA', 10006);
/**
* SMTP implementation of the PEAR Mail interface. Requires the Net_SMTP class.
* @access public
* @package Mail
* @version $Revision: 307488 $
*/
class Mail_smtp extends Mail {
/**
* SMTP connection object.
*
* @var object
* @access private
*/
var $_smtp = null;
/**
* The list of service extension parameters to pass to the Net_SMTP
* mailFrom() command.
* @var array
*/
var $_extparams = array();
/**
* The SMTP host to connect to.
* @var string
*/
var $host = 'localhost';
/**
* The port the SMTP server is on.
* @var integer
*/
var $port = 25;
/**
* Should SMTP authentication be used?
*
* This value may be set to true, false or the name of a specific
* authentication method.
*
* If the value is set to true, the Net_SMTP package will attempt to use
* the best authentication method advertised by the remote SMTP server.
*
* @var mixed
*/
var $auth = false;
/**
* The username to use if the SMTP server requires authentication.
* @var string
*/
var $username = '';
/**
* The password to use if the SMTP server requires authentication.
* @var string
*/
var $password = '';
/**
* Hostname or domain that will be sent to the remote SMTP server in the
* HELO / EHLO message.
*
* @var string
*/
var $localhost = 'localhost';
/**
* SMTP connection timeout value. NULL indicates no timeout.
*
* @var integer
*/
var $timeout = null;
/**
* Turn on Net_SMTP debugging?
*
* @var boolean $debug
*/
var $debug = false;
/**
* Indicates whether or not the SMTP connection should persist over
* multiple calls to the send() method.
*
* @var boolean
*/
var $persist = false;
/**
* Use SMTP command pipelining (specified in RFC 2920) if the SMTP server
* supports it. This speeds up delivery over high-latency connections. By
* default, use the default value supplied by Net_SMTP.
* @var bool
*/
var $pipelining;
/**
* Constructor.
*
* Instantiates a new Mail_smtp:: object based on the parameters
* passed in. It looks for the following parameters:
* host The server to connect to. Defaults to localhost.
* port The port to connect to. Defaults to 25.
* auth SMTP authentication. Defaults to none.
* username The username to use for SMTP auth. No default.
* password The password to use for SMTP auth. No default.
* localhost The local hostname / domain. Defaults to localhost.
* timeout The SMTP connection timeout. Defaults to none.
* verp Whether to use VERP or not. Defaults to false.
* DEPRECATED as of 1.2.0 (use setMailParams()).
* debug Activate SMTP debug mode? Defaults to false.
* persist Should the SMTP connection persist?
* pipelining Use SMTP command pipelining
*
* If a parameter is present in the $params array, it replaces the
* default.
*
* @param array Hash containing any parameters different from the
* defaults.
* @access public
*/
function Mail_smtp($params)
{
if (isset($params['host'])) $this->host = $params['host'];
if (isset($params['port'])) $this->port = $params['port'];
if (isset($params['auth'])) $this->auth = $params['auth'];
if (isset($params['username'])) $this->username = $params['username'];
if (isset($params['password'])) $this->password = $params['password'];
if (isset($params['localhost'])) $this->localhost = $params['localhost'];
if (isset($params['timeout'])) $this->timeout = $params['timeout'];
if (isset($params['debug'])) $this->debug = (bool)$params['debug'];
if (isset($params['persist'])) $this->persist = (bool)$params['persist'];
if (isset($params['pipelining'])) $this->pipelining = (bool)$params['pipelining'];
// Deprecated options
if (isset($params['verp'])) {
$this->addServiceExtensionParameter('XVERP', is_bool($params['verp']) ? null : $params['verp']);
}
register_shutdown_function(array(&$this, '_Mail_smtp'));
}
/**
* Destructor implementation to ensure that we disconnect from any
* potentially-alive persistent SMTP connections.
*/
function _Mail_smtp()
{
$this->disconnect();
}
/**
* Implements Mail::send() function using SMTP.
*
* @param mixed $recipients Either a comma-seperated list of recipients
* (RFC822 compliant), or an array of recipients,
* each RFC822 valid. This may contain recipients not
* specified in the headers, for Bcc:, resending
* messages, etc.
*
* @param array $headers The array of headers to send with the mail, in an
* associative array, where the array key is the
* header name (e.g., 'Subject'), and the array value
* is the header value (e.g., 'test'). The header
* produced from those values would be 'Subject:
* test'.
*
* @param string $body The full text of the message body, including any
* MIME parts, etc.
*
* @return mixed Returns true on success, or a PEAR_Error
* containing a descriptive error message on
* failure.
* @access public
*/
function send($recipients, $headers, $body)
{
/* If we don't already have an SMTP object, create one. */
$result = &$this->getSMTPObject();
//if (PEAR::isError($result)) {
if ($result === false) {
return $result;
}
if (!is_array($headers)) {
return Mail_smtp::raiseError('$headers must be an array');
}
$this->_sanitizeHeaders($headers);
$headerElements = $this->prepareHeaders($headers);
//if (is_a($headerElements, 'PEAR_Error')) {
if ($headerElements === false) {
$this->_smtp->rset();
return $headerElements;
}
list($from, $textHeaders) = $headerElements;
/* Since few MTAs are going to allow this header to be forged
* unless it's in the MAIL FROM: exchange, we'll use
* Return-Path instead of From: if it's set. */
if (!empty($headers['Return-Path'])) {
$from = $headers['Return-Path'];
}
if (!isset($from)) {
$this->_smtp->rset();
return Mail_smtp::raiseError('No From: address has been provided',
PEAR_MAIL_SMTP_ERROR_FROM);
}
$params = null;
if (!empty($this->_extparams)) {
foreach ($this->_extparams as $key => $val) {
$params .= ' ' . $key . (is_null($val) ? '' : '=' . $val);
}
}
//if (PEAR::isError($res = $this->_smtp->mailFrom($from, ltrim($params)))) {
if (($res = $this->_smtp->mailFrom($from, ltrim($params))) === false) {
$error = $this->_error("Failed to set sender: $from", $res);
$this->_smtp->rset();
return Mail_smtp::raiseError($error, PEAR_MAIL_SMTP_ERROR_SENDER);
}
$recipients = $this->parseRecipients($recipients);
//if (is_a($recipients, 'PEAR_Error')) {
if ($recipients === false) {
$this->_smtp->rset();
return $recipients;
}
// FIX: Cc and Bcc headers are sent, but we need to make sure that the recipient list contains them
foreach (array("CC", "cc", "Cc", "BCC", "Bcc", "bcc") as $key) {
if (!empty($headers[$key])) {
$extra_recipients = $this->parseRecipients($headers[$key]);
if ($extra_recipients === false) {
$this->_smtp->rset();
return $extra_recipients;
}
$recipients = array_merge($recipients, $extra_recipients);
}
}
// Remove repeated rcptTo
$recipients = array_unique($recipients);
foreach ($recipients as $recipient) {
$res = $this->_smtp->rcptTo($recipient);
//if (is_a($res, 'PEAR_Error')) {
if ($res === false) {
$error = $this->_error("Failed to add recipient: $recipient", $res);
$this->_smtp->rset();
return Mail_smtp::raiseError($error, PEAR_MAIL_SMTP_ERROR_RECIPIENT);
}
}
/* Send the message's headers and the body as SMTP data. */
$res = $this->_smtp->data($body, $textHeaders);
list(,$args) = $this->_smtp->getResponse();
if (preg_match("/Ok: queued as (.*)/", $args, $queued)) {
$this->queued_as = $queued[1];
}
/* we need the greeting; from it we can extract the authorative name of the mail server we've really connected to.
* ideal if we're connecting to a round-robin of relay servers and need to track which exact one took the email */
$this->greeting = $this->_smtp->getGreeting();
//if (is_a($res, 'PEAR_Error')) {
if ($res === false) {
$error = $this->_error('Failed to send data', $res);
$this->_smtp->rset();
return Mail_smtp::raiseError($error, PEAR_MAIL_SMTP_ERROR_DATA);
}
/* If persistent connections are disabled, destroy our SMTP object. */
if ($this->persist === false) {
$this->disconnect();
}
return true;
}
/**
* Connect to the SMTP server by instantiating a Net_SMTP object.
*
* @return mixed Returns a reference to the Net_SMTP object on success, or
* a PEAR_Error containing a descriptive error message on
* failure.
*
* @since 1.2.0
* @access public
*/
function &getSMTPObject()
{
if (is_object($this->_smtp) !== false) {
return $this->_smtp;
}
include_once 'include/Net/SMTP.php';
$this->_smtp = &new Net_SMTP($this->host,
$this->port,
$this->localhost,
$this->pipelining);
/* If we still don't have an SMTP object at this point, fail. */
if (is_object($this->_smtp) === false) {
return Mail_smtp::raiseError('Failed to create a Net_SMTP object',
PEAR_MAIL_SMTP_ERROR_CREATE);
}
/* Configure the SMTP connection. */
if ($this->debug) {
$this->_smtp->setDebug(true);
}
/* Attempt to connect to the configured SMTP server. */
//if (PEAR::isError($res = $this->_smtp->connect($this->timeout))) {
if (($res = $this->_smtp->connect($this->timeout)) === false) {
$error = $this->_error('Failed to connect to ' .
$this->host . ':' . $this->port,
$res);
return Mail_smtp::raiseError($error, PEAR_MAIL_SMTP_ERROR_CONNECT);
}
/* Attempt to authenticate if authentication has been enabled. */
if ($this->auth) {
$method = is_string($this->auth) ? $this->auth : '';
//if (PEAR::isError($res = $this->_smtp->auth($this->username, $this->password, $method))) {
if (($res = $this->_smtp->auth($this->username, $this->password, $method)) === false) {
$error = $this->_error("$method authentication failure",
$res);
$this->_smtp->rset();
return Mail_smtp::raiseError($error, PEAR_MAIL_SMTP_ERROR_AUTH);
}
}
return $this->_smtp;
}
/**
* Add parameter associated with a SMTP service extension.
*
* @param string Extension keyword.
* @param string Any value the keyword needs.
*
* @since 1.2.0
* @access public
*/
function addServiceExtensionParameter($keyword, $value = null)
{
$this->_extparams[$keyword] = $value;
}
/**
* Disconnect and destroy the current SMTP connection.
*
* @return boolean True if the SMTP connection no longer exists.
*
* @since 1.1.9
* @access public
*/
function disconnect()
{
/* If we have an SMTP object, disconnect and destroy it. */
if (is_object($this->_smtp) && $this->_smtp->disconnect()) {
$this->_smtp = null;
}
/* We are disconnected if we no longer have an SMTP object. */
return ($this->_smtp === null);
}
/**
* Build a standardized string describing the current SMTP error.
*
* @param string $text Custom string describing the error context.
* @param object $error Reference to the current PEAR_Error object.
*
* @return string A string describing the current SMTP error.
*
* @since 1.1.7
* @access private
*/
function _error($text, &$error)
{
/* Split the SMTP response into a code and a response string. */
list($code, $response) = $this->_smtp->getResponse();
/* Build our standardized error string. */
return $text
. ' [SMTP: ' . $error->getMessage()
. " (code: $code, response: $response)]";
}
/**
* Z-Push helper for error logging
* removing PEAR dependency
*
* @param string debug message
* @return boolean always false as there was an error
* @access private
*/
static function raiseError($message) {
ZLog::Write(LOGLEVEL_ERROR, "Mail<smtp> error: ". $message);
return false;
}
}

1254
sources/include/Net/SMTP.php Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,716 @@
<?php
/**
* Net_Socket
*
* PHP Version 4
*
* Copyright (c) 1997-2013 The PHP Group
*
* This source file is subject to version 2.0 of the PHP license,
* that is bundled with this package in the file LICENSE, and is
* available at through the world-wide-web at
* http://www.php.net/license/2_02.txt.
* If you did not receive a copy of the PHP license and are unable to
* obtain it through the world-wide-web, please send a note to
* license@php.net so we can mail you a copy immediately.
*
* Authors: Stig Bakken <ssb@php.net>
* Chuck Hagenbuch <chuck@horde.org>
*
* @category Net
* @package Net_Socket
* @author Stig Bakken <ssb@php.net>
* @author Chuck Hagenbuch <chuck@horde.org>
* @copyright 1997-2003 The PHP Group
* @license http://www.php.net/license/2_02.txt PHP 2.02
* @link http://pear.php.net/packages/Net_Socket
*/
/**
* Z-Push changes
*
* removed PEAR dependency by implementing own raiseError(), and defining OS_WINDOWS
*
* Reference implementation used:
* http://download.pear.php.net/package/Net_Socket-1.0.14.tgz
*
*
*/
//require_once 'PEAR.php';
if (substr(PHP_OS, 0, 3) == 'WIN') {
define('OS_WINDOWS', true);
} else {
define('OS_WINDOWS', false);
}
define('NET_SOCKET_READ', 1);
define('NET_SOCKET_WRITE', 2);
define('NET_SOCKET_ERROR', 4);
/**
* Generalized Socket class.
*
* @category Net
* @package Net_Socket
* @author Stig Bakken <ssb@php.net>
* @author Chuck Hagenbuch <chuck@horde.org>
* @copyright 1997-2003 The PHP Group
* @license http://www.php.net/license/2_02.txt PHP 2.02
* @link http://pear.php.net/packages/Net_Socket
*/
//class Net_Socket extends PEAR
class Net_Socket
{
/**
* Socket file pointer.
* @var resource $fp
*/
var $fp = null;
/**
* Whether the socket is blocking. Defaults to true.
* @var boolean $blocking
*/
var $blocking = true;
/**
* Whether the socket is persistent. Defaults to false.
* @var boolean $persistent
*/
var $persistent = false;
/**
* The IP address to connect to.
* @var string $addr
*/
var $addr = '';
/**
* The port number to connect to.
* @var integer $port
*/
var $port = 0;
/**
* Number of seconds to wait on socket operations before assuming
* there's no more data. Defaults to no timeout.
* @var integer|float $timeout
*/
var $timeout = null;
/**
* Number of bytes to read at a time in readLine() and
* readAll(). Defaults to 2048.
* @var integer $lineLength
*/
var $lineLength = 2048;
/**
* The string to use as a newline terminator. Usually "\r\n" or "\n".
* @var string $newline
*/
var $newline = "\r\n";
/**
* Connect to the specified port. If called when the socket is
* already connected, it disconnects and connects again.
*
* @param string $addr IP address or host name (may be with protocol prefix).
* @param integer $port TCP port number.
* @param boolean $persistent (optional) Whether the connection is
* persistent (kept open between requests
* by the web server).
* @param integer $timeout (optional) Connection socket timeout.
* @param array $options See options for stream_context_create.
*
* @access public
*
* @return boolean|PEAR_Error True on success or a PEAR_Error on failure.
*/
function connect($addr, $port = 0, $persistent = null,
$timeout = null, $options = null)
{
if (is_resource($this->fp)) {
@fclose($this->fp);
$this->fp = null;
}
if (!$addr) {
return $this->raiseError('$addr cannot be empty');
} else if (strspn($addr, ':.0123456789') == strlen($addr)) {
$this->addr = strpos($addr, ':') !== false ? '['.$addr.']' : $addr;
} else {
$this->addr = $addr;
}
$this->port = $port % 65536;
if ($persistent !== null) {
$this->persistent = $persistent;
}
$openfunc = $this->persistent ? 'pfsockopen' : 'fsockopen';
$errno = 0;
$errstr = '';
$old_track_errors = @ini_set('track_errors', 1);
if ($timeout <= 0) {
$timeout = @ini_get('default_socket_timeout');
}
if ($options && function_exists('stream_context_create')) {
$context = stream_context_create($options);
// Since PHP 5 fsockopen doesn't allow context specification
if (function_exists('stream_socket_client')) {
$flags = STREAM_CLIENT_CONNECT;
if ($this->persistent) {
$flags = STREAM_CLIENT_PERSISTENT;
}
$addr = $this->addr . ':' . $this->port;
$fp = stream_socket_client($addr, $errno, $errstr,
$timeout, $flags, $context);
} else {
$fp = @$openfunc($this->addr, $this->port, $errno,
$errstr, $timeout, $context);
}
} else {
$fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $timeout);
}
if (!$fp) {
if ($errno == 0 && !strlen($errstr) && isset($php_errormsg)) {
$errstr = $php_errormsg;
}
@ini_set('track_errors', $old_track_errors);
return $this->raiseError($errstr, $errno);
}
@ini_set('track_errors', $old_track_errors);
$this->fp = $fp;
$this->setTimeout();
return $this->setBlocking($this->blocking);
}
/**
* Disconnects from the peer, closes the socket.
*
* @access public
* @return mixed true on success or a PEAR_Error instance otherwise
*/
function disconnect()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
@fclose($this->fp);
$this->fp = null;
return true;
}
/**
* Set the newline character/sequence to use.
*
* @param string $newline Newline character(s)
* @return boolean True
*/
function setNewline($newline)
{
$this->newline = $newline;
return true;
}
/**
* Find out if the socket is in blocking mode.
*
* @access public
* @return boolean The current blocking mode.
*/
function isBlocking()
{
return $this->blocking;
}
/**
* Sets whether the socket connection should be blocking or
* not. A read call to a non-blocking socket will return immediately
* if there is no data available, whereas it will block until there
* is data for blocking sockets.
*
* @param boolean $mode True for blocking sockets, false for nonblocking.
*
* @access public
* @return mixed true on success or a PEAR_Error instance otherwise
*/
function setBlocking($mode)
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$this->blocking = $mode;
stream_set_blocking($this->fp, (int)$this->blocking);
return true;
}
/**
* Sets the timeout value on socket descriptor,
* expressed in the sum of seconds and microseconds
*
* @param integer $seconds Seconds.
* @param integer $microseconds Microseconds, optional.
*
* @access public
* @return mixed True on success or false on failure or
* a PEAR_Error instance when not connected
*/
function setTimeout($seconds = null, $microseconds = null)
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
if ($seconds === null && $microseconds === null) {
$seconds = (int) $this->timeout;
$microseconds = (int) (($this->timeout - $seconds) * 1000000);
} else {
$this->timeout = $seconds + $microseconds/1000000;
}
if ($this->timeout > 0) {
return stream_set_timeout($this->fp, (int) $seconds, (int) $microseconds);
}
else {
return false;
}
}
/**
* Sets the file buffering size on the stream.
* See php's stream_set_write_buffer for more information.
*
* @param integer $size Write buffer size.
*
* @access public
* @return mixed on success or an PEAR_Error object otherwise
*/
function setWriteBuffer($size)
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$returned = stream_set_write_buffer($this->fp, $size);
if ($returned == 0) {
return true;
}
return $this->raiseError('Cannot set write buffer.');
}
/**
* Returns information about an existing socket resource.
* Currently returns four entries in the result array:
*
* <p>
* timed_out (bool) - The socket timed out waiting for data<br>
* blocked (bool) - The socket was blocked<br>
* eof (bool) - Indicates EOF event<br>
* unread_bytes (int) - Number of bytes left in the socket buffer<br>
* </p>
*
* @access public
* @return mixed Array containing information about existing socket
* resource or a PEAR_Error instance otherwise
*/
function getStatus()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
return stream_get_meta_data($this->fp);
}
/**
* Get a specified line of data
*
* @param int $size Reading ends when size - 1 bytes have been read,
* or a newline or an EOF (whichever comes first).
* If no size is specified, it will keep reading from
* the stream until it reaches the end of the line.
*
* @access public
* @return mixed $size bytes of data from the socket, or a PEAR_Error if
* not connected. If an error occurs, FALSE is returned.
*/
function gets($size = null)
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
if (is_null($size)) {
return @fgets($this->fp);
} else {
return @fgets($this->fp, $size);
}
}
/**
* Read a specified amount of data. This is guaranteed to return,
* and has the added benefit of getting everything in one fread()
* chunk; if you know the size of the data you're getting
* beforehand, this is definitely the way to go.
*
* @param integer $size The number of bytes to read from the socket.
*
* @access public
* @return $size bytes of data from the socket, or a PEAR_Error if
* not connected.
*/
function read($size)
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
return @fread($this->fp, $size);
}
/**
* Write a specified amount of data.
*
* @param string $data Data to write.
* @param integer $blocksize Amount of data to write at once.
* NULL means all at once.
*
* @access public
* @return mixed If the socket is not connected, returns an instance of
* PEAR_Error.
* If the write succeeds, returns the number of bytes written.
* If the write fails, returns false.
* If the socket times out, returns an instance of PEAR_Error.
*/
function write($data, $blocksize = null)
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
if (is_null($blocksize) && !OS_WINDOWS) {
$written = @fwrite($this->fp, $data);
// Check for timeout or lost connection
if (!$written) {
$meta_data = $this->getStatus();
if (!is_array($meta_data)) {
return $meta_data; // PEAR_Error
}
if (!empty($meta_data['timed_out'])) {
return $this->raiseError('timed out');
}
}
return $written;
} else {
if (is_null($blocksize)) {
$blocksize = 1024;
}
$pos = 0;
$size = strlen($data);
while ($pos < $size) {
$written = @fwrite($this->fp, substr($data, $pos, $blocksize));
// Check for timeout or lost connection
if (!$written) {
$meta_data = $this->getStatus();
if (!is_array($meta_data)) {
return $meta_data; // PEAR_Error
}
if (!empty($meta_data['timed_out'])) {
return $this->raiseError('timed out');
}
return $written;
}
$pos += $written;
}
return $pos;
}
}
/**
* Write a line of data to the socket, followed by a trailing newline.
*
* @param string $data Data to write
*
* @access public
* @return mixed fwrite() result, or PEAR_Error when not connected
*/
function writeLine($data)
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
return fwrite($this->fp, $data . $this->newline);
}
/**
* Tests for end-of-file on a socket descriptor.
*
* Also returns true if the socket is disconnected.
*
* @access public
* @return bool
*/
function eof()
{
return (!is_resource($this->fp) || feof($this->fp));
}
/**
* Reads a byte of data
*
* @access public
* @return 1 byte of data from the socket, or a PEAR_Error if
* not connected.
*/
function readByte()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
return ord(@fread($this->fp, 1));
}
/**
* Reads a word of data
*
* @access public
* @return 1 word of data from the socket, or a PEAR_Error if
* not connected.
*/
function readWord()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$buf = @fread($this->fp, 2);
return (ord($buf[0]) + (ord($buf[1]) << 8));
}
/**
* Reads an int of data
*
* @access public
* @return integer 1 int of data from the socket, or a PEAR_Error if
* not connected.
*/
function readInt()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$buf = @fread($this->fp, 4);
return (ord($buf[0]) + (ord($buf[1]) << 8) +
(ord($buf[2]) << 16) + (ord($buf[3]) << 24));
}
/**
* Reads a zero-terminated string of data
*
* @access public
* @return string, or a PEAR_Error if
* not connected.
*/
function readString()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$string = '';
while (($char = @fread($this->fp, 1)) != "\x00") {
$string .= $char;
}
return $string;
}
/**
* Reads an IP Address and returns it in a dot formatted string
*
* @access public
* @return Dot formatted string, or a PEAR_Error if
* not connected.
*/
function readIPAddress()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$buf = @fread($this->fp, 4);
return sprintf('%d.%d.%d.%d', ord($buf[0]), ord($buf[1]),
ord($buf[2]), ord($buf[3]));
}
/**
* Read until either the end of the socket or a newline, whichever
* comes first. Strips the trailing newline from the returned data.
*
* @access public
* @return All available data up to a newline, without that
* newline, or until the end of the socket, or a PEAR_Error if
* not connected.
*/
function readLine()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$line = '';
$timeout = time() + $this->timeout;
while (!feof($this->fp) && (!$this->timeout || time() < $timeout)) {
$line .= @fgets($this->fp, $this->lineLength);
if (substr($line, -1) == "\n") {
return rtrim($line, $this->newline);
}
}
return $line;
}
/**
* Read until the socket closes, or until there is no more data in
* the inner PHP buffer. If the inner buffer is empty, in blocking
* mode we wait for at least 1 byte of data. Therefore, in
* blocking mode, if there is no data at all to be read, this
* function will never exit (unless the socket is closed on the
* remote end).
*
* @access public
*
* @return string All data until the socket closes, or a PEAR_Error if
* not connected.
*/
function readAll()
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$data = '';
while (!feof($this->fp)) {
$data .= @fread($this->fp, $this->lineLength);
}
return $data;
}
/**
* Runs the equivalent of the select() system call on the socket
* with a timeout specified by tv_sec and tv_usec.
*
* @param integer $state Which of read/write/error to check for.
* @param integer $tv_sec Number of seconds for timeout.
* @param integer $tv_usec Number of microseconds for timeout.
*
* @access public
* @return False if select fails, integer describing which of read/write/error
* are ready, or PEAR_Error if not connected.
*/
function select($state, $tv_sec, $tv_usec = 0)
{
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
$read = null;
$write = null;
$except = null;
if ($state & NET_SOCKET_READ) {
$read[] = $this->fp;
}
if ($state & NET_SOCKET_WRITE) {
$write[] = $this->fp;
}
if ($state & NET_SOCKET_ERROR) {
$except[] = $this->fp;
}
if (false === ($sr = stream_select($read, $write, $except,
$tv_sec, $tv_usec))) {
return false;
}
$result = 0;
if (count($read)) {
$result |= NET_SOCKET_READ;
}
if (count($write)) {
$result |= NET_SOCKET_WRITE;
}
if (count($except)) {
$result |= NET_SOCKET_ERROR;
}
return $result;
}
/**
* Turns encryption on/off on a connected socket.
*
* @param bool $enabled Set this parameter to true to enable encryption
* and false to disable encryption.
* @param integer $type Type of encryption. See stream_socket_enable_crypto()
* for values.
*
* @see http://se.php.net/manual/en/function.stream-socket-enable-crypto.php
* @access public
* @return false on error, true on success and 0 if there isn't enough data
* and the user should try again (non-blocking sockets only).
* A PEAR_Error object is returned if the socket is not
* connected
*/
function enableCrypto($enabled, $type)
{
if (version_compare(phpversion(), "5.1.0", ">=")) {
if (!is_resource($this->fp)) {
return $this->raiseError('not connected');
}
return @stream_socket_enable_crypto($this->fp, $enabled, $type);
} else {
$msg = 'Net_Socket::enableCrypto() requires php version >= 5.1.0';
return $this->raiseError($msg);
}
}
/**
* Z-Push helper for error logging
* removing PEAR dependency
*
* @param string debug message
* @return boolean always false as there was an error
* @access private
*/
function raiseError($message) {
ZLog::Write(LOGLEVEL_ERROR, "Net_Socket error: ". $message);
return false;
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -356,7 +356,7 @@ class Mail_mimeDecode
if ($this->_rfc822_bodies) { if ($this->_rfc822_bodies) {
$encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit'; $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
$charset = isset($return->ctype_parameters['charset']) ? $return->ctype_parameters['charset'] : $this->_charset; $charset = isset($return->ctype_parameters['charset']) ? $return->ctype_parameters['charset'] : $this->_charset;
$return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding, $charset) : $body); $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding, $charset, false) : $body);
} }
$obj = new Mail_mimeDecode($body); $obj = new Mail_mimeDecode($body);
@ -371,7 +371,7 @@ class Mail_mimeDecode
$content_transfer_encoding['value'] = '7bit'; $content_transfer_encoding['value'] = '7bit';
// if there is no explicit charset, then don't try to convert to default charset, and make sure that only text mimetypes are converted // if there is no explicit charset, then don't try to convert to default charset, and make sure that only text mimetypes are converted
$charset = (isset($return->ctype_parameters['charset']) && ((isset($return->ctype_primary) && $return->ctype_primary == 'text') || !isset($return->ctype_primary)) )? $return->ctype_parameters['charset']: ''; $charset = (isset($return->ctype_parameters['charset']) && ((isset($return->ctype_primary) && $return->ctype_primary == 'text') || !isset($return->ctype_primary)) )? $return->ctype_parameters['charset']: '';
$this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $content_transfer_encoding['value'], $charset) : $body) : null; $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $content_transfer_encoding['value'], $charset, false) : $body) : null;
break; break;
} }
@ -711,8 +711,11 @@ class Mail_mimeDecode
// Remove white space between encoded-words // Remove white space between encoded-words
$input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input); $input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input);
$encodedwords = false;
$charset = '';
// For each encoded-word... // For each encoded-word...
while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches)) { while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches)) {
$encodedwords = true;
$encoded = $matches[1]; $encoded = $matches[1];
$charset = $matches[2]; $charset = $matches[2];
@ -727,12 +730,17 @@ class Mail_mimeDecode
case 'q': case 'q':
$text = str_replace('_', ' ', $text); $text = str_replace('_', ' ', $text);
preg_match_all('/=([a-f0-9]{2})/i', $text, $matches); preg_match_all('/=([a-f0-9]{2})/i', $text, $matches);
foreach($matches[1] as $value) foreach($matches[1] as $value) {
$text = str_replace('='.$value, chr(hexdec($value)), $text); $text = str_replace('='.$value, chr(hexdec($value)), $text);
}
break; break;
} }
$input = str_replace($encoded, $this->_fromCharset($charset, $text), $input); $input = str_replace($encoded, $this->_autoconvert_encoding($text, $charset), $input);
}
if (!$encodedwords) {
$input = $this->_autoconvert_encoding($input, $charset);
} }
return $input; return $input;
@ -744,33 +752,76 @@ class Mail_mimeDecode
* *
* @param string Input body to decode * @param string Input body to decode
* @param string Encoding type to use. * @param string Encoding type to use.
* @param string Charset
* @param boolean Must try to autodetect the real charset used
* @return string Decoded body * @return string Decoded body
* @access private * @access private
*/ */
function _decodeBody($input, $encoding = '7bit', $charset = '') function _decodeBody($input, $encoding = '7bit', $charset = '', $detectCharset = true)
{ {
switch (strtolower($encoding)) { switch (strtolower($encoding)) {
case '7bit':
return $this->_fromCharset($charset, $input);;
break;
case '8bit':
return $this->_fromCharset($charset, $input);
break;
case 'quoted-printable': case 'quoted-printable':
return $this->_fromCharset($charset, $this->_quotedPrintableDecode($input)); $input_decoded = $this->_quotedPrintableDecode($input);
return $detectCharset ? $this->_autoconvert_encoding($input_decoded, $charset) : $input_decoded;
break; break;
case 'base64': case 'base64':
return $this->_fromCharset($charset, base64_decode($input)); $input_decoded = base64_decode($input);
return $detectCharset ? $this->_autoconvert_encoding($input_decoded, $charset) : $input_decoded;
break; break;
case '7bit':
case '8bit':
default: default:
return $input; return $detectCharset ? $this->_autoconvert_encoding($input, $charset) : $input;
break;
} }
} }
/**
* Error handler dummy for _autoconvert_encoding
*
* @param integer $errno
* @param string $errstr
* @return boolean true
* @access public static
*/
static function _iconv_notice_handler($errno, $errstr) {
return true;
}
/**
* Autoconvert the text from any encoding. THIS WILL NEVER WORK 100%.
* Will ignore the E_NOTICE for iconv when detecting ilegal charsets
*
* @param string $input Input string to convert
* @param string $supposed_encoding Encoding that the text is possibly using
* @return string Converted string
* @access private
*/
function _autoconvert_encoding($input, $supposed_encoding = "UTF-8") {
$input_converted = $input;
if (function_exists("mb_detect_order")) {
$mb_order = array_merge(array($supposed_encoding), mb_detect_order());
set_error_handler('Mail_mimeDecode::_iconv_notice_handler');
try {
$input_converted = iconv(mb_detect_encoding($input, $mb_order, true), $this->_charset, $input);
}
catch(Exception $ex) {
$this->raiseError($ex->getMessage());
}
restore_error_handler();
if (strlen($input_converted) == 0 && strlen($input) != 0) {
ZLog::Write(LOGLEVEL_INFO, "Mail_mimeDecode() - Text cannot be correctly decoded, using original text. Expect encoding errors");
$input_converted = mb_convert_encoding($input, 'UTF-8', 'UTF-8');
}
}
return $input_converted;
}
/** /**
* Given a quoted-printable string, this * Given a quoted-printable string, this
* function will decode and return it. * function will decode and return it.
@ -865,6 +916,34 @@ class Mail_mimeDecode
return $files; return $files;
} }
/**
* Get all parts in the message with specified type and concatenate them together, unless the
* Content-Disposition is 'attachment', in which case the text is apparently an attachment
*
* @param string $message mimedecode message(part)
* @param string $message message subtype
* @param string &$body body reference
*
* @return void
* @access public
*/
static function getBodyRecursive($message, $subtype, &$body) {
if(!isset($message->ctype_primary)) return;
if(strcasecmp($message->ctype_primary,"text")==0 && strcasecmp($message->ctype_secondary,$subtype)==0 && isset($message->body))
$body .= $message->body;
if(strcasecmp($message->ctype_primary,"multipart")==0 && isset($message->parts) && is_array($message->parts)) {
foreach($message->parts as $part) {
// Check testing/samples/m1009.txt
// Content-Type: text/plain; charset=us-ascii; name="hareandtoroise.txt" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="hareandtoroise.txt"
// We don't want to show that file text (outlook doesn't show it), so if we have content-disposition we don't apply recursivity
if(!isset($part->disposition)) {
Mail_mimeDecode::getBodyRecursive($part, $subtype, $body);
}
}
}
}
/** /**
* getSendArray() returns the arguments required for Mail::send() * getSendArray() returns the arguments required for Mail::send()
* used to build the arguments for a mail::send() call * used to build the arguments for a mail::send() call
@ -1026,25 +1105,6 @@ class Mail_mimeDecode
return $return; return $return;
} }
/**
* Z-Push helper to decode text
*
* @param string current charset of input
* @param string input
* @return string XML version of input
* @access private
*/
function _fromCharset($charset, $input) {
if($charset == '' || (strtolower($charset) == $this->_charset))
return $input;
// all ISO-8859-1 are converted as if they were Windows-1252 - see Mantis #456
if (strtolower($charset) == 'iso-8859-1')
$charset = 'Windows-1252';
return @iconv($charset, $this->_charset. "//TRANSLIT", $input);
}
/** /**
* Z-Push helper for error logging * Z-Push helper for error logging
* removing PEAR dependency * removing PEAR dependency

1270
sources/include/mimePart.php Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,37 +1,48 @@
<?php <?php
// +-----------------------------------------------------------------------+ /**
// | Copyright (c) 2001-2002, Richard Heyes | * RFC 822 Email address list validation Utility
// | All rights reserved. | *
// | | * PHP versions 4 and 5
// | Redistribution and use in source and binary forms, with or without | *
// | modification, are permitted provided that the following conditions | * LICENSE:
// | are met: | *
// | | * Copyright (c) 2001-2010, Richard Heyes
// | o Redistributions of source code must retain the above copyright | * All rights reserved.
// | notice, this list of conditions and the following disclaimer. | *
// | o Redistributions in binary form must reproduce the above copyright | * Redistribution and use in source and binary forms, with or without
// | notice, this list of conditions and the following disclaimer in the | * modification, are permitted provided that the following conditions
// | documentation and/or other materials provided with the distribution.| * are met:
// | o The names of the authors may not be used to endorse or promote | *
// | products derived from this software without specific prior written | * o Redistributions of source code must retain the above copyright
// | permission. | * notice, this list of conditions and the following disclaimer.
// | | * o Redistributions in binary form must reproduce the above copyright
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | * notice, this list of conditions and the following disclaimer in the
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | * documentation and/or other materials provided with the distribution.
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | * o The names of the authors may not be used to endorse or promote
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | * products derived from this software without specific prior written
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | * permission.
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | *
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// | | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// +-----------------------------------------------------------------------+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// | Authors: Richard Heyes <richard@phpguru.org> | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// | Chuck Hagenbuch <chuck@horde.org> | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// +-----------------------------------------------------------------------+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @category Mail
* @package Mail
* @author Richard Heyes <richard@phpguru.org>
* @author Chuck Hagenbuch <chuck@horde.org
* @copyright 2001-2010 Richard Heyes
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version CVS: $Id: RFC822.php 294749 2010-02-08 08:22:25Z clockwerx $
* @link http://pear.php.net/package/Mail/
*/
/** /**
* RFC 822 Email address list validation Utility * RFC 822 Email address list validation Utility
@ -52,7 +63,7 @@
* *
* @author Richard Heyes <richard@phpguru.org> * @author Richard Heyes <richard@phpguru.org>
* @author Chuck Hagenbuch <chuck@horde.org> * @author Chuck Hagenbuch <chuck@horde.org>
* @version $Revision: 1.23 $ * @version $Revision: 294749 $
* @license BSD * @license BSD
* @package Mail * @package Mail
*/ */
@ -183,6 +194,7 @@ class Mail_RFC822 {
$this->address = preg_replace('/\r\n(\t| )+/', ' ', $this->address); $this->address = preg_replace('/\r\n(\t| )+/', ' ', $this->address);
while ($this->address = $this->_splitAddresses($this->address)); while ($this->address = $this->_splitAddresses($this->address));
if ($this->address === false || isset($this->error)) { if ($this->address === false || isset($this->error)) {
//require_once 'PEAR.php'; //require_once 'PEAR.php';
return $this->raiseError($this->error); return $this->raiseError($this->error);
@ -342,22 +354,39 @@ class Mail_RFC822 {
} }
/** /**
* Checks if a string has an unclosed quotes or not. * Checks if a string has unclosed quotes or not.
* *
* @access private * @access private
* @param string $string The string to check. * @param string $string The string to check.
* @return boolean True if there are unclosed quotes inside the string, false otherwise. * @return boolean True if there are unclosed quotes inside the string,
* false otherwise.
*/ */
function _hasUnclosedQuotes($string) function _hasUnclosedQuotes($string)
{ {
$string = explode('"', $string); $string = trim($string);
$string_cnt = count($string); $iMax = strlen($string);
$in_quote = false;
$i = $slashes = 0;
for ($i = 0; $i < (count($string) - 1); $i++) for (; $i < $iMax; ++$i) {
if (substr($string[$i], -1) == '\\') switch ($string[$i]) {
$string_cnt--; case '\\':
++$slashes;
break;
return ($string_cnt % 2 === 0); case '"':
if ($slashes % 2 == 0) {
$in_quote = !$in_quote;
}
// Fall through to default action below.
default:
$slashes = 0;
break;
}
}
return $in_quote;
} }
/** /**
@ -511,6 +540,7 @@ class Mail_RFC822 {
{ {
// Splits on one or more Tab or space. // Splits on one or more Tab or space.
$parts = preg_split('/[ \\x09]+/', $phrase, -1, PREG_SPLIT_NO_EMPTY); $parts = preg_split('/[ \\x09]+/', $phrase, -1, PREG_SPLIT_NO_EMPTY);
$phrase_parts = array(); $phrase_parts = array();
while (count($parts) > 0){ while (count($parts) > 0){
$phrase_parts[] = $this->_splitCheck($parts, ' '); $phrase_parts[] = $this->_splitCheck($parts, ' ');
@ -553,6 +583,7 @@ class Mail_RFC822 {
// Validation has been turned off; assume the atom is okay. // Validation has been turned off; assume the atom is okay.
return true; return true;
} }
// Check for any char from ASCII 0 - ASCII 127 // Check for any char from ASCII 0 - ASCII 127
if (!preg_match('/^[\\x00-\\x7E]+$/i', $atom, $matches)) { if (!preg_match('/^[\\x00-\\x7E]+$/i', $atom, $matches)) {
return false; return false;
@ -567,6 +598,7 @@ class Mail_RFC822 {
if (preg_match('/[\\x00-\\x1F]+/', $atom)) { if (preg_match('/[\\x00-\\x1F]+/', $atom)) {
return false; return false;
} }
return true; return true;
} }
@ -615,8 +647,8 @@ class Mail_RFC822 {
$comment = $this->_splitCheck($parts, ')'); $comment = $this->_splitCheck($parts, ')');
$comments[] = $comment; $comments[] = $comment;
// +1 is for the trailing ) // +2 is for the brackets
$_mailbox = substr($_mailbox, strpos($_mailbox, $comment)+strlen($comment)+1); $_mailbox = substr($_mailbox, strpos($_mailbox, '('.$comment)+strlen($comment)+2);
} else { } else {
break; break;
} }
@ -642,7 +674,6 @@ class Mail_RFC822 {
} }
if ($this->_validatePhrase($phrase) === false || ($route_addr = $this->_validateRouteAddr($route_addr)) === false) { if ($this->_validatePhrase($phrase) === false || ($route_addr = $this->_validateRouteAddr($route_addr)) === false) {
return false; return false;
} }

707
sources/include/z_RTF.php Normal file
View file

@ -0,0 +1,707 @@
<?php
/*
This class contains code from rtfclass.php that was written by Markus Fischer and placed by him under
GPLv2 License.
=======================================NOTES FROM ORIGINAL AUTHOR====================================
Rich Text Format - Parsing Class
================================
(c) 2000 Markus Fischer
<mfischer@josefine.ben.tuwien.ac.at>
http://josefine.ben.tuwien.ac.at/~mfischer/
Latest versions of this class can always be found at
http://josefine.ben.tuwien.ac.at/~mfischer/developing/php/rtf/rtfclass.phps
Testing suite is available at
http://josefine.ben.tuwien.ac.at/~mfischer/developing/php/rtf/
License: GPLv2
Specification:
http://msdn.microsoft.com/library/default.asp?URL=/library/specs/rtfspec.htm
General Notes:
==============
Unknown or unspupported control symbols are silently gnored
Group stacking is still not supported :(
group stack logic implemented; however not really used yet
=====================================================================================================
It was modified by me (Andreas Brodowski) to allow compressed RTF being uncompressed by code I ported from
Java to PHP and adapted according the needs of Z-Push.
Currently it is being used to detect empty RTF Streams from Nokia Phones in MfE Clients
It needs to be used by other backend writers that needs to have notes in calendar, appointment or tasks
objects to be written to their databases since devices send them usually in RTF Format... With Zarafa
you can write them directly to DB and Zarafa is doing the conversion job. Other Groupware systems usually
don't have this possibility...
*/
class rtf {
var $LZRTF_HDR_DATA = "{\\rtf1\\ansi\\mac\\deff0\\deftab720{\\fonttbl;}{\\f0\\fnil \\froman \\fswiss \\fmodern \\fscript \\fdecor MS Sans SerifSymbolArialTimes New RomanCourier{\\colortbl\\red0\\green0\\blue0\n\r\\par \\pard\\plain\\f0\\fs20\\b\\i\\u\\tab\\tx";
var $LZRTF_HDR_LEN = 207;
var $CRC32_TABLE = array( 0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3,
0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD,0xE7B82D07,0x90BF1D91,
0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D,0x6DDDE4EB,0xF4D4B551,0x83D385C7,
0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC,0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5,
0x3B6E20C8,0x4C69105E,0xD56041E4,0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,
0x35B5A8FA,0x42B2986C,0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59,
0x26D930AC,0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F,
0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB,0xB6662D3D,
0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F,0x9FBFE4A5,0xE8B8D433,
0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB,0x086D3D2D,0x91646C97,0xE6635C01,
0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E,0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,
0x65B0D9C6,0x12B7E950,0x8BBEB8EA,0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65,
0x4DB26158,0x3AB551CE,0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB,
0x4369E96A,0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9,
0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409,0xCE61E49F,
0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81,0xB7BD5C3B,0xC0BA6CAD,
0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739,0x9DD277AF,0x04DB2615,0x73DC1683,
0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8,0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1,
0xF00F9344,0x8708A3D2,0x1E01F268,0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7,
0xFED41B76,0x89D32BE0,0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5,
0xD6D6A3E8,0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B,
0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF,0x4669BE79,
0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703,0x220216B9,0x5505262F,
0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7,0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D,
0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A,0x9C0906A9,0xEB0E363F,0x72076785,0x05005713,
0x95BF4A82,0xE2B87A14,0x7BB12BAE,0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21,
0x86D3D2D4,0xF1D4E242,0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,
0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45,
0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D,0x3E6E77DB,
0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5,0x47B2CF7F,0x30B5FFE9,
0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605,0xCDD70693,0x54DE5729,0x23D967BF,
0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94,0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D,
);
var $rtf; // rtf core stream
var $rtf_len; // length in characters of the stream (get performace due avoiding calling strlen everytime)
var $err = array(); // array of error message, no entities on no error
var $wantXML = false; // convert to XML
var $wantHTML = false; // convert to HTML
var $wantASCII = false; // convert to HTML
// the only variable which should be accessed from the outside
var $out; // output data stream (depends on which $wantXXXXX is set to true
var $outstyles; // htmlified styles (generated after parsing if wantHTML
var $styles; // if wantHTML, stylesheet definitions are put in here
// internal parser variables --------------------------------
// control word variables
var $cword; // holds the current (or last) control word, depending on $cw
var $cw; // are we currently parsing a control word ?
var $cfirst; // could this be the first character ? so watch out for control symbols
var $flags = array(); // parser flags
var $queue; // every character which is no sepcial char, not belongs to a control word/symbol; is generally considered being 'plain'
var $stack = array(); // group stack
/* keywords which don't follw the specification (used by Word '97 - 2000) */
// not yet used
var $control_exception = array(
"clFitText",
"clftsWidth(-?[0-9]+)?",
"clNoWrap(-?[0-9]+)?",
"clwWidth(-?[0-9]+)?",
"tdfrmtxtBottom(-?[0-9]+)?",
"tdfrmtxtLeft(-?[0-9]+)?",
"tdfrmtxtRight(-?[0-9]+)?",
"tdfrmtxtTop(-?[0-9]+)?",
"trftsWidthA(-?[0-9]+)?",
"trftsWidthB(-?[0-9]+)?",
"trftsWidth(-?[0-9]+)?",
"trwWithA(-?[0-9]+)?",
"trwWithB(-?[0-9]+)?",
"trwWith(-?[0-9]+)?",
"spectspecifygen(-?[0-9]+)?",
);
var $charset_table = array(
"0" => "ANSI",
"1" => "Default",
"2" => "Symbol",
"77" => "Mac",
"128" => "Shift Jis",
"129" => "Hangul",
"130" => "Johab",
"134" => "GB2312",
"136" => "Big5",
"161" => "Greek",
"162" => "Turkish",
"163" => "Vietnamese",
"177" => "Hebrew",
"178" => "Arabic",
"179" => "Arabic Traditional",
"180" => "Arabic user",
"181" => "Hebrew user",
"186" => "Baltic",
"204" => "Russian",
"222" => "Thai",
"238" => "Eastern European",
"255" => "PC 437",
"255" => "OEM",
);
/* note: the only conversion table used */
var $fontmodifier_table = array(
"bold" => "b",
"italic" => "i",
"underlined" => "u",
"strikethru" => "strike",
);
function rtf() {
$this->rtf_len = 0;
$this->rtf = '';
$this->out = '';
}
// loadrtf - load the raw rtf data to be converted by this class
// data = the raw rtf
function loadrtf($data) {
if (($this->rtf = $this->uncompress($data))) {
$this->rtf_len = strlen($this->rtf);
};
if($this->rtf_len == 0) {
debugLog("No data in stream found");
return false;
};
return true;
}
function output($typ) {
switch($typ) {
case "ascii": $this->wantASCII = true; break;
case "xml": $this->wantXML = true; break;
case "html": $this->wantHTML = true; break;
default: break;
}
}
// uncompress - uncompress compressed rtf data
// src = the compressed raw rtf in LZRTF format
function uncompress($src) {
$header = unpack("LcSize/LuSize/Lmagic/Lcrc32",substr($src,0,16));
$in = 16;
if ($header['cSize'] != strlen($src)-4) {
debugLog("Stream too short");
return false;
}
if ($header['crc32'] != $this->LZRTFCalcCRC32($src,16,(($header['cSize']+4))-16)) {
debugLog("CRC MISMATCH");
return false;
}
if ($header['magic'] == 0x414c454d) { // uncompressed RTF - return as is.
$dest = substr($src,$in,$header['uSize']);
} else if ($header['magic'] == 0x75465a4c) { // compressed RTF - uncompress.
$dst = $this->LZRTF_HDR_DATA;
$out = $this->LZRTF_HDR_LEN;
$oblen = $this->LZRTF_HDR_LEN + $header['uSize'];
$flagCount = 0;
$flags = 0;
while ($out<$oblen) {
$flags = ($flagCount++ % 8 == 0) ? ord($src{$in++}) : $flags >> 1;
if (($flags & 1) == 1) {
$offset = ord($src{$in++});
$length = ord($src{$in++});
$offset = ($offset << 4) | ($length >> 4);
$length = ($length & 0xF) + 2;
$offset = (int)($out / 4096) * 4096 + $offset;
if ($offset >= $out) $offset -= 4096;
$end = $offset + $length;
while ($offset < $end) {
$dst{$out++} = $dst{$offset++};
};
} else {
$dst{$out++} = $src{$in++};
}
}
$src = $dst;
$dest = substr($src,$this->LZRTF_HDR_LEN,$header['uSize']);
} else { // unknown magic - returfn false (please report if this ever happens)
debugLog("Unknown Magic");
return false;
}
return $dest;
}
// LZRTFCalcCRC32 - calculates the CRC32 of the LZRTF data part
// buf = the whole rtf data part
// off = start point of crc calculation
// len = length of data to calculate CRC for
// function is necessary since in RTF there is no XOR 0xffffffff being done (said to be 0x00 unsafe CRC32 calculation
function LZRTFCalcCRC32($buf, $off, $len) {
$c=0;
$end = $off + $len;
for($i=$off;$i < $end;$i++) {
$c=$this->CRC32_TABLE[($c ^ ord($buf{$i})) & 0xFF] ^ (($c >> 8) & 0x00ffffff);
}
return $c;
}
function parserInit() { /* Default values according to the specs */
$this->flags = array(
"fontsize" => 24,
"beginparagraph" => true,
);
}
function parseControl($control, $parameter) {
switch ($control) {
case "fonttbl": // font table definition start
$this->flags["fonttbl"] = true; // signal fonttable control words they are allowed to behave as expected
break;
case "f": // define or set font
if($this->flags["fonttbl"]) { // if its set, the fonttable definition is written to; else its read from
$this->flags["fonttbl_current_write"] = $parameter;
} else {
$this->flags["fonttbl_current_read"] = $parameter;
}
break;
case "fcharset": // this is for preparing flushQueue; it then moves the Queue to $this->fonttable .. instead to formatted output
$this->flags["fonttbl_want_fcharset"] = $parameter;
break;
case "fs": // sets the current fontsize; is used by stylesheets (which are therefore generated on the fly
$this->flags["fontsize"] = $parameter;
break;
case "qc": // handle center alignment
$this->flags["alignment"] = "center";
break;
case "qr": // handle right alignment
$this->flags["alignment"] = "right";
break;
case "pard": // reset paragraph settings (only alignment)
$this->flags["alignment"] = "";
break;
case "par": // define new paragraph (for now, thats a simple break in html) begin new line
$this->flags["beginparagraph"] = true;
if($this->wantHTML) {
$this->out .= "</div>";
}
if($this->wantASCII) {
$this->out .= "\n";
}
break;
case "bnone": // bold
$parameter = "0";
case "b":
// haven'y yet figured out WHY I need a (string)-cast here ... hm
if((string)$parameter == "0")
$this->flags["bold"] = false;
else
$this->flags["bold"] = true;
break;
case "ulnone": // underlined
$parameter = "0";
case "ul":
if((string)$parameter == "0")
$this->flags["underlined"] = false;
else
$this->flags["underlined"] = true;
break;
case "inone": // italic
$parameter = "0";
case "i":
if((string)$parameter == "0")
$this->flags["italic"] = false;
else
$this->flags["italic"] = true;
break;
case "strikenone": // strikethru
$parameter = "0";
case "strike":
if((string)$parameter == "0")
$this->flags["strikethru"] = false;
else
$this->flags["strikethru"] = true;
break;
case "plain": // reset all font modifiers and fontsize to 12
$this->flags["bold"] = false;
$this->flags["italic"] = false;
$this->flags["underlined"] = false;
$this->flags["strikethru"] = false;
$this->flags["fontsize"] = 12;
$this->flags["subscription"] = false;
$this->flags["superscription"] = false;
break;
case "subnone": // subscription
$parameter = "0";
case "sub":
if((string)$parameter == "0")
$this->flags["subscription"] = false;
else
$this->flags["subscription"] = true;
break;
case "supernone": // superscription
$parameter = "0";
case "super":
if((string)$parameter == "0")
$this->flags["superscription"] = false;
else
$this->flags["superscription"] = true;
break;
}
}
/*
Dispatch the control word to the output stream
*/
function flushControl() {
if(preg_match("/^([A-Za-z]+)(-?[0-9]*) ?$/", $this->cword, $match)) {
$this->parseControl($match[1], $match[2]);
if($this->wantXML) {
$this->out.="<control word=\"".$match[1]."\"";
if(strlen($match[2]) > 0)
$this->out.=" param=\"".$match[2]."\"";
$this->out.="/>";
}
}
}
/*
If output stream supports comments, dispatch it
*/
function flushComment($comment) {
if($this->wantXML || $this->wantHTML) {
$this->out.="<!-- ".$comment." -->";
}
}
/*
Dispatch start/end of logical rtf groups (not every output type needs it; merely debugging purpose)
*/
function flushGroup($state) {
if($state == "open") { /* push onto the stack */
array_push($this->stack, $this->flags);
if($this->wantXML)
$this->out.="<group>";
}
if($state == "close") { /* pop from the stack */
$this->last_flags = $this->flags;
$this->flags = array_pop($this->stack);
$this->flags["fonttbl_current_write"] = ""; // on group close, no more fontdefinition will be written to this id
// this is not really the right way to do it !
// of course a '}' not necessarily donates a fonttable end; a fonttable
// group at least *can* contain sub-groups
// therefore an stacked approach is heavily needed
$this->flags["fonttbl"] = false; // no matter what you do, if a group closes, its fonttbl definition is closed too
if($this->wantXML)
$this->out.="</group>";
}
}
function flushHead() {
if($this->wantXML)
$this->out.="<rtf>";
}
function flushBottom() {
if($this->wantXML)
$this->out.="</rtf>";
}
function checkHtmlSpanContent($command) {
reset($this->fontmodifier_table);
while(list($rtf, $html) = each($this->fontmodifier_table)) {
if($this->flags[$rtf] == true) {
if($command == "start")
$this->out .= "<".$html.">";
else
$this->out .= "</".$html.">";
}
}
}
/*
flush text in queue
*/
function flushQueue() {
if(strlen($this->queue)) {
// processing logic
if (isset($this->flags["fonttbl_want_fcharset"]) &&
preg_match("/^[0-9]+$/", $this->flags["fonttbl_want_fcharset"])) {
$this->fonttable[$this->flags["fonttbl_want_fcharset"]]["charset"] = $this->queue;
$this->flags["fonttbl_want_fcharset"] = "";
$this->queue = "";
}
// output logic
if (strlen($this->queue)) {
/*
Everything which passes this is (or, at leat, *should*) be only outputted plaintext
Thats why we can safely add the css-stylesheet when using wantHTML
*/
if($this->wantXML)
$this->out.= "<plain>".$this->queue."</plain>";
else if($this->wantHTML) {
// only output html if a valid (for now, just numeric;) fonttable is given
if (!isset($this->flags["fonttbl_current_read"])) $this->flags["fonttbl_current_read"] = "";
if(preg_match("/^[0-9]+$/", $this->flags["fonttbl_current_read"])) {
if($this->flags["beginparagraph"] == true) {
$this->flags["beginparagraph"] = false;
$this->out .= "<div align=\"";
switch($this->flags["alignment"]) {
case "right":
$this->out .= "right";
break;
case "center":
$this->out .= "center";
break;
case "left":
default:
$this->out .= "left";
}
$this->out .= "\">";
}
/* define new style for that span */
$this->styles["f".$this->flags["fonttbl_current_read"]."s".$this->flags["fontsize"]] = "font-family:".$this->fonttable[$this->flags["fonttbl_current_read"]]["charset"]." font-size:".$this->flags["fontsize"].";";
/* write span start */
$this->out .= "<span class=\"f".$this->flags["fonttbl_current_read"]."s".$this->flags["fontsize"]."\">";
/* check if the span content has a modifier */
$this->checkHtmlSpanContent("start");
/* write span content */
$this->out .= $this->queue;
/* close modifiers */
$this->checkHtmlSpanContent("stop");
/* close span */
"</span>";
}
}
$this->queue = "";
}
}
}
/*
handle special charactes like \'ef
*/
function flushSpecial($special) {
if(strlen($special) == 2) {
if($this->wantASCII)
$this->out .= chr(hexdec('0x'.$special));
else if($this->wantXML)
$this->out .= "<special value=\"".$special."\"/>";
else if($this->wantHTML){
$this->out .= "<special value=\"".$special."\"/>";
switch($special) {
case "c1": $this->out .= "&Aacute;"; break;
case "e1": $this->out .= "&aacute;"; break;
case "c0": $this->out .= "&Agrave;"; break;
case "e0": $this->out .= "&agrave;"; break;
case "c9": $this->out .= "&Eacute;"; break;
case "e9": $this->out .= "&eacute;"; break;
case "c8": $this->out .= "&Egrave;"; break;
case "e8": $this->out .= "&egrave;"; break;
case "cd": $this->out .= "&Iacute;"; break;
case "ed": $this->out .= "&iacute;"; break;
case "cc": $this->out .= "&Igrave;"; break;
case "ec": $this->out .= "&igrave;"; break;
case "d3": $this->out .= "&Oacute;"; break;
case "f3": $this->out .= "&oacute;"; break;
case "d2": $this->out .= "&Ograve;"; break;
case "f2": $this->out .= "&ograve;"; break;
case "da": $this->out .= "&Uacute;"; break;
case "fa": $this->out .= "&uacute;"; break;
case "d9": $this->out .= "&Ugrave;"; break;
case "f9": $this->out .= "&ugrave;"; break;
case "80": $this->out .= "&#8364;"; break;
case "d1": $this->out .= "&Ntilde;"; break;
case "f1": $this->out .= "&ntilde;"; break;
case "c7": $this->out .= "&Ccedil;"; break;
case "e7": $this->out .= "&ccedil;"; break;
case "dc": $this->out .= "&Uuml;"; break;
case "fc": $this->out .= "&uuml;"; break;
case "bf": $this->out .= "&#191;"; break;
case "a1": $this->out .= "&#161;"; break;
case "b7": $this->out .= "&middot;"; break;
case "a9": $this->out .= "&copy;"; break;
case "ae": $this->out .= "&reg;"; break;
case "ba": $this->out .= "&ordm;"; break;
case "aa": $this->out .= "&ordf;"; break;
case "b2": $this->out .= "&sup2;"; break;
case "b3": $this->out .= "&sup3;"; break;
}
}
}
}
/*
Output errors at end
*/
function flushErrors() {
if(count($this->err) > 0) {
if($this->wantXML) {
$this->out .= "<errors>";
while(list($num,$value) = each($this->err)) {
$this->out .= "<message>".$value."</message>";
}
$this->out .= "</errors>";
}
}
}
function makeStyles() {
$this->outstyles = "<style type=\"text/css\"><!--\n";
reset($this->styles);
while(list($stylename, $styleattrib) = each($this->styles)) {
$this->outstyles .= ".".$stylename." { ".$styleattrib." }\n";
}
$this->outstyles .= "--></style>\n";
}
function parse() {
$this->parserInit();
$i = 0;
$this->cw= false; // flag if control word is currently parsed
$this->cfirst = false; // first control character ?
$this->cword = ""; // last or current control word (depends on $this->cw
$this->queue = ""; // plain text data found during parsing
$this->flushHead();
while($i < $this->rtf_len) {
switch($this->rtf[$i]) {
case "{":
if($this->cw) {
$this->flushControl();
$this->cw = false;
$this->cfirst = false;
} else
$this->flushQueue();
$this->flushGroup("open");
break;
case "}":
if($this->cw) {
$this->flushControl();
$this->cw = false;
$this->cfirst = false;
} else
$this->flushQueue();
$this->flushGroup("close");
break;
case "\\":
if($this->cfirst) { // catches '\\'
$this->queue .= "\\"; // replaced single quotes
$this->cfirst = false;
$this->cw = false;
break;
}
if($this->cw) {
$this->flushControl();
} else
$this->flushQueue();
$this->cw = true;
$this->cfirst = true;
$this->cword = "";
break;
default:
if((ord($this->rtf[$i]) == 10) || (ord($this->rtf[$i]) == 13)) break; // eat line breaks
if($this->cw) { // active control word ?
/*
Watch the RE: there's an optional space at the end which IS part of
the control word (but actually its ignored by flushControl)
*/
if(preg_match("/^[a-zA-Z0-9-]?$/", $this->rtf[$i])) { // continue parsing
$this->cword .= $this->rtf[$i];
$this->cfirst = false;
} else {
/*
Control word could be a 'control symbol', like \~ or \* etc.
*/
$specialmatch = false;
if($this->cfirst) {
if($this->rtf[$i] == '\'') { // expect to get some special chars
$this->flushQueue();
$this->flushSpecial($this->rtf[$i+1].$this->rtf[$i+2]);
$i+=2;
$specialmatch = true;
$this->cw = false;
$this->cfirst = false;
$this->cword = "";
} else
if(preg_match("/^[{}\*]$/", $this->rtf[$i])) {
$this->flushComment("control symbols not yet handled");
$specialmatch = true;
}
$this->cfirst = false;
} else {
if($this->rtf[$i] == ' ') { // space delimtes control words, so just discard it and flush the controlword
$this->cw = false;
$this->flushControl();
break;
}
}
if(!$specialmatch) {
$this->flushControl();
$this->cw = false;
$this->cfirst = false;
/*
The current character is a delimeter, but is NOT
part of the control word so we hop one step back
in the stream and process it again
*/
$i--;
}
}
} else {
// < and > need translation before putting into queue when XML or HTML is wanted
if(($this->wantHTML) || ($this->wantXML)) {
switch($this->rtf[$i]) {
case "<":
$this->queue .= "&lt;";
break;
case ">":
$this->queue .= "&gt;";
break;
default:
$this->queue .= $this->rtf[$i];
break;
}
} else
$this->queue .= $this->rtf[$i];
}
}
$i++;
}
$this->flushQueue();
$this->flushErrors();
$this->flushBottom();
if($this->wantHTML) {
$this->makeStyles();
}
}
}
?>

View file

@ -0,0 +1,972 @@
<?php
/**
* A Class for connecting to a caldav server
*
* Based on caldav-client-v2.php by Andrew McMillan <andrew@mcmillan.net.nz>
* but using cURL instead of home-brew request construction. cURL code re-used
* from carddav.php by Jean-Louis Dupond. Additional bugfixes to
* caldav-client-v2.php by xbgmsharp <xbgmsharp@gmail.com>.
*
* Copyright Andrew McMillan (original caldav-client-v2.php), Jean-Louis Dupond (cURL code), xbgmsharp (bugfixes)
* Copyright Thorsten Köster
* License GNU LGPL version 3 or later (http://www.gnu.org/licenses/lgpl-3.0.txt)
*/
require_once('XMLDocument.php');
/**
* A class for holding basic calendar information
*/
class CalendarInfo {
public $url;
public $displayname;
public $getctag;
public $id;
function __construct( $url, $displayname = null, $getctag = null, $id = null ) {
$this->url = $url;
$this->displayname = $displayname;
$this->getctag = $getctag;
$this->id = $id;
}
function __toString() {
return( '(URL: '.$this->url.' Ctag: '.$this->getctag.' Displayname: '.$this->displayname .')'. "\n" );
}
}
/**
* A class for accessing DAViCal via CalDAV, as a client
*
* @package awl
*/
class CalDAVClient {
/**
* Server, username, password, calendar
*
* @var string
*/
protected $server, $base_url, $user, $pass, $auth;
/**
* The principal-URL we're using
*/
protected $principal_url;
/**
* The calendar-URL we're using
*/
protected $calendar_url;
/**
* The calendar-home-set we're using
*/
protected $calendar_home_set;
/**
* The calendar_urls we have discovered
*/
protected $calendar_urls;
/**
* The useragent which is send to the caldav server
*
* @var string
*/
const USERAGENT = 'ModifiedDAViCalClient';
protected $headers = array();
protected $xmlResponse = ""; // xml received
protected $httpResponseCode = 0; // http response code
protected $httpResponseHeaders = "";
protected $httpResponseBody = "";
protected $parser; // our XML parser object
/**
* CardDAV server connection (curl handle)
*
* @var resource
*/
private $curl;
private $synctoken = array();
/**
* Constructor, initialises the class
*
* @param string $caldav_url The URL for the calendar server
* @param string $user The name of the user logging in
* @param string $pass The password for that user
*/
function __construct( $caldav_url, $user, $pass ) {
$this->user = $user;
$this->pass = $pass;
$this->auth = $user . ':' . $pass;
$this->headers = array();
$parsed_url = parse_url($caldav_url);
if ($parsed_url == FALSE) {
ZLog::Write(LOGLEVEL_ERROR, sprintf('Couldn\'t parse URL: %s', $caldav_url));
} else
$this->server = $parsed_url['scheme'] . '://' . $parsed_url['host'] . ':' . $parsed_url['port'];
$this->base_url = $parsed_url['path'];
// $this->base_url .= !empty($parsed_url['query']) ? '?' . $parsed_url['query'] : '';
// $this->base_url .= !empty($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
if (substr($this->base_url, -1, 1) !== '/') {
$this->base_url = $this->base_url . '/';
}
}
/**
* Adds an If-Match or If-None-Match header
*
* @param bool $match to Match or Not to Match, that is the question!
* @param string $etag The etag to match / not match against.
*/
function SetMatch( $match, $etag = '*' ) {
$this->headers['match'] = sprintf( "%s-Match: \"%s\"", ($match ? "If" : "If-None"), trim($etag,'"'));
}
/**
* Add a Depth: header. Valid values are 0, 1 or infinity
*
* @param int $depth The depth, default to infinity
*/
function SetDepth( $depth = '0' ) {
$this->headers['depth'] = 'Depth: '. ($depth == '1' ? "1" : ($depth == 'infinity' ? $depth : "0") );
}
/**
* Set the calendar_url we will be using for a while.
*
* @param string $url The calendar_url
*/
function SetCalendar( $url ) {
$this->calendar_url = $url;
}
/**
* Split response into httpResponse and xmlResponse
*
* @param string Response from server
*/
function ParseResponse( $response ) {
$pos = strpos($response, '<?xml');
if ($pos !== false) {
$this->xmlResponse = trim(substr($response, $pos));
$this->xmlResponse = preg_replace('{>[^>]*$}s', '>',$this->xmlResponse );
$parser = xml_parser_create_ns('UTF-8');
xml_parser_set_option ( $parser, XML_OPTION_SKIP_WHITE, 1 );
xml_parser_set_option ( $parser, XML_OPTION_CASE_FOLDING, 0 );
if ( xml_parse_into_struct( $parser, $this->xmlResponse, $this->xmlnodes, $this->xmltags ) === 0 ) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("XML parsing error: %s - %s", xml_get_error_code($parser), xml_error_string(xml_get_error_code($parser))));
// debug_print_backtrace();
// echo "\nNodes array............................................................\n"; print_r( $this->xmlnodes );
// echo "\nTags array............................................................\n"; print_r( $this->xmltags );
ZLog::Write(LOGLEVEL_DEBUG, sprintf("XML Reponse:\n%s\n", $this->xmlResponse));
}
xml_parser_free($parser);
}
}
public function curl_init() {
if (empty($this->curl)) {
$this->curl = curl_init();
curl_setopt($this->curl, CURLOPT_HEADER, true);
curl_setopt($this->curl, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($this->curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($this->curl, CURLOPT_USERAGENT, self::USERAGENT);
if ($this->auth !== null) {
curl_setopt($this->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
curl_setopt($this->curl, CURLOPT_USERPWD, $this->auth);
}
}
}
/**
* Send a request to the server
*
* @param string $url The URL to make the request to
*
* @return string The content of the response from the server
*/
function DoRequest($url, $method, $content = null, $content_type = "text/plain") {
$this->curl_init();
if ( !isset($url) ) $url = $this->base_url;
$url = preg_replace('{^https?://[^/]+}', '', $url);
$url = $this->server . $url;
curl_setopt($this->curl, CURLOPT_URL, $url);
curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $method);
if ($content !== null)
{
curl_setopt($this->curl, CURLOPT_POST, true);
curl_setopt($this->curl, CURLOPT_POSTFIELDS, $content);
}
else
{
curl_setopt($this->curl, CURLOPT_POST, false);
curl_setopt($this->curl, CURLOPT_POSTFIELDS, null);
}
$headers = array();
$headers['content-type'] = 'Content-type: ' . $content_type;
foreach( $this->headers as $ii => $head ) {
$headers[$ii] = $head;
}
curl_setopt($this->curl, CURLOPT_HTTPHEADER, $headers);
$this->xmlResponse = '';
//ZLog::Write(LOGLEVEL_DEBUG, sprintf("Request:\n%s\n", $content));
$response = curl_exec($this->curl);
//ZLog::Write(LOGLEVEL_DEBUG, sprintf("Reponse:\n%s\n", $response));
$header_size = curl_getinfo($this->curl, CURLINFO_HEADER_SIZE);
$this->httpResponseCode = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
$this->httpResponseHeaders = trim(substr($response, 0, $header_size));
$this->httpResponseBody = substr($response, $header_size);
$this->headers = array(); // reset the headers array for our next request
$this->ParseResponse($this->httpResponseBody);
return $response;
}
/**
* Send an OPTIONS request to the server
*
* @param string $url The URL to make the request to
*
* @return array The allowed options
*/
function DoOptionsRequest( $url = null ) {
$headers = $this->DoRequest($url, "OPTIONS");
$options_header = preg_replace( '/^.*Allow: ([a-z, ]+)\r?\n.*/is', '$1', $headers );
$options = array_flip( preg_split( '/[, ]+/', $options_header ));
return $options;
}
/**
* Send an XML request to the server (e.g. PROPFIND, REPORT, MKCALENDAR)
*
* @param string $method The method (PROPFIND, REPORT, etc) to use with the request
* @param string $xml The XML to send along with the request
* @param string $url The URL to make the request to
*
* @return array An array of the allowed methods
*/
function DoXMLRequest( $request_method, $xml, $url = null ) {
return $this->DoRequest($url, $request_method, $xml, "text/xml");
}
/**
* Get a single item from the server.
*
* @param string $url The URL to GET
*/
function DoGETRequest( $url ) {
return $this->DoRequest($url, "GET");
}
/**
* Get the HEAD of a single item from the server.
*
* @param string $url The URL to HEAD
*/
function DoHEADRequest( $url ) {
return $this->DoRequest($url, "HEAD");
}
/**
* PUT a text/icalendar resource, returning the etag
*
* @param string $url The URL to make the request to
* @param string $icalendar The iCalendar resource to send to the server
* @param string $etag The etag of an existing resource to be overwritten, or '*' for a new resource.
*
* @return string The content of the response from the server
*/
function DoPUTRequest( $url, $icalendar, $etag = null ) {
if ( $etag != null ) {
$this->SetMatch( ($etag != '*'), $etag );
}
$this->DoRequest($url, "PUT", $icalendar, 'text/calendar; encoding="utf-8"');
$etag = null;
if ( preg_match( '{^ETag:\s+"([^"]*)"\s*$}im', $this->httpResponseHeaders, $matches ) ) {
$etag = $matches[1];
}
if ( !isset($etag) || $etag == '' ) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("No etag in:\n%s\n", $this->httpResponseHeaders));
$save_request = $this->httpRequest;
$save_response_headers = $this->httpResponseHeaders;
$this->DoHEADRequest( $url );
if ( preg_match( '{^Etag:\s+"([^"]*)"\s*$}im', $this->httpResponseHeaders, $matches ) ) {
$etag = $matches[1];
}
if ( !isset($etag) || $etag == '' ) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Still No etag in:\n%s\n", $this->httpResponseHeaders));
}
$this->httpRequest = $save_request;
$this->httpResponseHeaders = $save_response_headers;
}
return $etag;
}
/**
* DELETE a text/icalendar resource
*
* @param string $url The URL to make the request to
* @param string $etag The etag of an existing resource to be deleted, or '*' for any resource at that URL.
*
* @return int The HTTP Result Code for the DELETE
*/
function DoDELETERequest( $url, $etag = null ) {
if ( $etag != null ) {
$this->SetMatch( true, $etag );
}
$this->DoRequest($url, "DELETE");
return $this->httpResponseCode;
}
/**
* Get a single item from the server.
*
* @param string $url The URL to PROPFIND on
*/
function DoPROPFINDRequest( $url, $props, $depth = 0 ) {
$this->SetDepth($depth);
$xml = new XMLDocument( array( 'DAV:' => '', 'urn:ietf:params:xml:ns:caldav' => 'C' ) );
$prop = new XMLElement('prop');
foreach( $props AS $v ) {
$xml->NSElement($prop,$v);
}
$this->DoRequest($url, "PROPFIND", $xml->Render('propfind',$prop), "text/xml");
return $this->xmlResponse;
}
/**
* Get/Set the Principal URL
*
* @param $url string The Principal URL to set
*/
function PrincipalURL( $url = null ) {
if ( isset($url) ) {
$this->principal_url = $url;
}
return $this->principal_url;
}
/**
* Get/Set the calendar-home-set URL
*
* @param $url array of string The calendar-home-set URLs to set
*/
function CalendarHomeSet( $urls = null ) {
if ( isset($urls) ) {
if ( !is_array($urls) ) {
$urls = array($urls);
}
$this->calendar_home_set = $urls;
}
return $this->calendar_home_set;
}
/**
* Get/Set the calendar-home-set URL
*
* @param $urls array of string The calendar URLs to set
*/
function CalendarUrls( $urls = null ) {
if ( isset($urls) ) {
if ( !is_array($urls) ) {
$urls = array($urls);
}
$this->calendar_urls = $urls;
}
return $this->calendar_urls;
}
/**
* Return the first occurrence of an href inside the named tag.
*
* @param string $tagname The tag name to find the href inside of
*/
function HrefValueInside( $tagname ) {
foreach( $this->xmltags[$tagname] AS $k => $v ) {
$j = $v + 1;
if ( $this->xmlnodes[$j]['tag'] == 'DAV::href' ) {
return rawurldecode($this->xmlnodes[$j]['value']);
}
}
return null;
}
/**
* Return the href containing this property. Except only if it's inside a status != 200
*
* @param string $tagname The tag name of the property to find the href for
* @param integer $which Which instance of the tag should we use
*/
function HrefForProp( $tagname, $i = 0 ) {
if ( isset($this->xmltags[$tagname]) && isset($this->xmltags[$tagname][$i]) ) {
$j = $this->xmltags[$tagname][$i];
while( $j-- > 0 && $this->xmlnodes[$j]['tag'] != 'DAV::href' ) {
// printf( "Node[$j]: %s\n", $this->xmlnodes[$j]['tag']);
if ( $this->xmlnodes[$j]['tag'] == 'DAV::status' && $this->xmlnodes[$j]['value'] != 'HTTP/1.1 200 OK' ) {
return null;
}
}
// printf( "Node[$j]: %s\n", $this->xmlnodes[$j]['tag']);
if ( $j > 0 && isset($this->xmlnodes[$j]['value']) ) {
// printf( "Value[$j]: %s\n", $this->xmlnodes[$j]['value']);
return rawurldecode($this->xmlnodes[$j]['value']);
}
}
else {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("xmltags[$tagname] or xmltags[$tagname][$i] is not set."));
}
return null;
}
/**
* Return the href which has a resourcetype of the specified type
*
* @param string $tagname The tag name of the resourcetype to find the href for
* @param integer $which Which instance of the tag should we use
*/
function HrefForResourcetype( $tagname, $i = 0 ) {
if ( isset($this->xmltags[$tagname]) && isset($this->xmltags[$tagname][$i]) ) {
$j = $this->xmltags[$tagname][$i];
while( $j-- > 0 && $this->xmlnodes[$j]['tag'] != 'DAV::resourcetype' );
if ( $j > 0 ) {
while( $j-- > 0 && $this->xmlnodes[$j]['tag'] != 'DAV::href' );
if ( $j > 0 && isset($this->xmlnodes[$j]['value']) ) {
return rawurldecode($this->xmlnodes[$j]['value']);
}
}
}
return null;
}
/**
* Return the <prop> ... </prop> of a propstat where the status is OK
*
* @param string $nodenum The node number in the xmlnodes which is the href
*/
function GetOKProps( $nodenum ) {
$props = null;
$level = $this->xmlnodes[$nodenum]['level'];
$status = '';
while ( $this->xmlnodes[++$nodenum]['level'] >= $level ) {
if ( $this->xmlnodes[$nodenum]['tag'] == 'DAV::propstat' ) {
if ( $this->xmlnodes[$nodenum]['type'] == 'open' ) {
$props = array();
$status = '';
} else {
if ( $status == 'HTTP/1.1 200 OK' ) {
break;
}
}
} elseif ( !isset($this->xmlnodes[$nodenum]) || !is_array($this->xmlnodes[$nodenum]) ) {
break;
} elseif ( $this->xmlnodes[$nodenum]['tag'] == 'DAV::status' ) {
$status = $this->xmlnodes[$nodenum]['value'];
} else {
$props[] = $this->xmlnodes[$nodenum];
}
}
return $props;
}
/**
* Attack the given URL in an attempt to find a principal URL
*
* @param string $url The URL to find the principal-URL from
*/
function FindPrincipal( $url=null ) {
$xml = $this->DoPROPFINDRequest( $url, array('resourcetype', 'current-user-principal', 'owner', 'principal-URL', 'urn:ietf:params:xml:ns:caldav:calendar-home-set'), 1);
$principal_url = $this->HrefForProp('DAV::principal');
if ( !isset($principal_url) ) {
foreach( array('DAV::current-user-principal', 'DAV::principal-URL', 'DAV::owner') AS $href ) {
if ( !isset($principal_url) ) {
$principal_url = $this->HrefValueInside($href);
}
}
}
return $this->PrincipalURL($principal_url);
}
/**
* Attack the given URL in an attempt to find a principal URL
*
* @param string $url The URL to find the calendar-home-set from
*/
function FindCalendarHome( $recursed=false ) {
if ( !isset($this->principal_url) ) {
$this->FindPrincipal();
}
if ( $recursed ) {
$this->DoPROPFINDRequest( $this->principal_url, array('urn:ietf:params:xml:ns:caldav:calendar-home-set'), 0);
}
$calendar_home = array();
foreach( $this->xmltags['urn:ietf:params:xml:ns:caldav:calendar-home-set'] AS $k => $v ) {
if ( $this->xmlnodes[$v]['type'] != 'open' ) {
continue;
}
while( $this->xmlnodes[++$v]['type'] != 'close' && $this->xmlnodes[$v]['tag'] != 'urn:ietf:params:xml:ns:caldav:calendar-home-set' ) {
// printf( "Tag: '%s' = '%s'\n", $this->xmlnodes[$v]['tag'], $this->xmlnodes[$v]['value']);
if ( $this->xmlnodes[$v]['tag'] == 'DAV::href' && isset($this->xmlnodes[$v]['value']) ) {
$calendar_home[] = rawurldecode($this->xmlnodes[$v]['value']);
}
}
}
if ( !$recursed && count($calendar_home) < 1 ) {
$calendar_home = $this->FindCalendarHome(true);
}
return $this->CalendarHomeSet($calendar_home);
}
/**
* Find the calendars, from the calendar_home_set
*/
function FindCalendars( $recursed=false ) {
if ( !isset($this->calendar_home_set[0]) ) {
$this->FindCalendarHome();
}
$this->DoPROPFINDRequest( $this->calendar_home_set[0], array('resourcetype','displayname','http://calendarserver.org/ns/:getctag'), 1);
$calendars = array();
if ( isset($this->xmltags['urn:ietf:params:xml:ns:caldav:calendar']) ) {
$calendar_urls = array();
foreach( $this->xmltags['urn:ietf:params:xml:ns:caldav:calendar'] AS $k => $v ) {
$calendar_urls[$this->HrefForProp('urn:ietf:params:xml:ns:caldav:calendar', $k)] = 1;
}
foreach( $this->xmltags['DAV::href'] AS $i => $hnode ) {
$href = rawurldecode($this->xmlnodes[$hnode]['value']);
if ( !isset($calendar_urls[$href]) ) {
continue;
}
// printf("Seems '%s' is a calendar.\n", $href );
$calendar = new CalendarInfo($href);
$ok_props = $this->GetOKProps($hnode);
foreach( $ok_props AS $v ) {
// printf("Looking at: %s[%s]\n", $href, $v['tag'] );
switch( $v['tag'] ) {
case 'http://calendarserver.org/ns/:getctag':
$calendar->getctag = $v['value'];
break;
case 'DAV::displayname':
$calendar->displayname = $v['value'];
break;
}
}
$calendar->id = rtrim(str_replace($this->calendar_home_set[0], "", $calendar->url), "/");
$calendars[] = $calendar;
}
}
return $this->CalendarUrls($calendars);
}
/**
* Find the calendars, from the calendar_home_set
*/
function GetCalendarDetails( $url = null ) {
if ( isset($url) ) {
$this->SetCalendar($url);
}
if ( !isset($this->calendar_home_set[0]) ) {
$this->FindCalendarHome();
}
$calendar_properties = array( 'resourcetype', 'displayname', 'http://calendarserver.org/ns/:getctag', 'urn:ietf:params:xml:ns:caldav:calendar-timezone', 'supported-report-set' );
$this->DoPROPFINDRequest( $this->calendar_url, $calendar_properties, 0);
$hnode = $this->xmltags['DAV::href'][0];
$href = rawurldecode($this->xmlnodes[$hnode]['value']);
$calendar = new CalendarInfo($href);
$ok_props = $this->GetOKProps($hnode);
foreach( $ok_props AS $k => $v ) {
$name = preg_replace( '{^.*:}', '', $v['tag'] );
if ( isset($v['value'] ) ) {
$calendar->{$name} = $v['value'];
} /* else {
printf( "Calendar property '%s' has no text content\n", $v['tag'] );
}*/
}
$calendar->id = rtrim(str_replace($this->calendar_home_set[0], "", $calendar->url), "/");
return $calendar;
}
/**
* Get all etags for a calendar
*/
function GetCollectionETags( $url = null ) {
if ( isset($url) ) {
$this->SetCalendar($url);
}
$this->DoPROPFINDRequest( $this->calendar_url, array('getetag'), 1);
$etags = array();
if ( isset($this->xmltags['DAV::getetag']) ) {
foreach( $this->xmltags['DAV::getetag'] AS $k => $v ) {
$href = $this->HrefForProp('DAV::getetag', $k);
if ( isset($href) && isset($this->xmlnodes[$v]['value']) ) {
$etags[$href] = $this->xmlnodes[$v]['value'];
}
}
}
return $etags;
}
/**
* Get a bunch of events for a calendar with a calendar-multiget report
*/
function CalendarMultiget( $event_hrefs, $url = null ) {
if ( isset($url) ) {
$this->SetCalendar($url);
}
$hrefs = '';
foreach( $event_hrefs AS $k => $href ) {
$href = str_replace( rawurlencode('/'),'/',rawurlencode($href));
$hrefs .= '<href>'.$href.'</href>';
}
$body = <<<EOXML
<?xml version="1.0" encoding="utf-8" ?>
<C:calendar-multiget xmlns="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
<prop><getetag/><C:calendar-data/></prop>
$hrefs
</C:calendar-multiget>
EOXML;
$this->DoRequest($this->calendar_url, "REPORT", $body, "text/xml");
$events = array();
if ( isset($this->xmltags['urn:ietf:params:xml:ns:caldav:calendar-data']) ) {
foreach( $this->xmltags['urn:ietf:params:xml:ns:caldav:calendar-data'] AS $k => $v ) {
$href = $this->HrefForProp('urn:ietf:params:xml:ns:caldav:calendar-data', $k);
// echo "Calendar-data:\n"; print_r($this->xmlnodes[$v]);
$events[$href] = $this->xmlnodes[$v]['value'];
}
} else {
foreach( $event_hrefs AS $k => $href ) {
$this->DoGETRequest($href);
$events[$href] = $this->httpResponseBody;
}
}
return $events;
}
/**
* Given XML for a calendar query, return an array of the events (/todos) in the
* response. Each event in the array will have a 'href', 'etag' and '$response_type'
* part, where the 'href' is relative to the calendar and the '$response_type' contains the
* definition of the calendar data in iCalendar format.
*
* @param string $filter XML fragment which is the <filter> element of a calendar-query
* @param string $url The URL of the calendar, or empty/null to use the 'current' calendar_url
*
* @return array An array of the relative URLs, etags, and events from the server. Each element of the array will
* be an array with 'href', 'etag' and 'data' elements, corresponding to the URL, the server-supplied
* etag (which only varies when the data changes) and the calendar data in iCalendar format.
*/
function DoCalendarQuery( $filter, $url = null ) {
if ( !empty($url) ) {
$this->SetCalendar($url);
}
$body = <<<EOXML
<?xml version="1.0" encoding="utf-8" ?>
<C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:prop>
<C:calendar-data/>
<D:getetag/>
</D:prop>
$filter
</C:calendar-query>
EOXML;
$this->SetDepth(1);
$this->DoRequest($this->calendar_url, "REPORT", $body, "text/xml");
$report = array();
foreach( $this->xmlnodes as $k => $v ) {
switch( $v['tag'] ) {
case 'DAV::response':
if ( $v['type'] == 'open' ) {
$response = array();
} elseif ( $v['type'] == 'close' ) {
$report[] = $response;
}
break;
case 'DAV::href':
$response['href'] = basename( rawurldecode($v['value']) );
break;
case 'DAV::getetag':
$response['etag'] = preg_replace('/^"?([^"]+)"?/', '$1', $v['value']);
break;
case 'urn:ietf:params:xml:ns:caldav:calendar-data':
$response['data'] = $v['value'];
break;
}
}
return $report;
}
/**
* Get the events in a range from $start to $finish. The dates should be in the
* format yyyymmddThhmmssZ and should be in GMT. The events are returned as an
* array of event arrays. Each event array will have a 'href', 'etag' and 'event'
* part, where the 'href' is relative to the calendar and the event contains the
* definition of the event in iCalendar format.
*
* @param timestamp $start The start time for the period
* @param timestamp $finish The finish time for the period
* @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''.
*
* @return array An array of the relative URLs, etags, and events, returned from DoCalendarQuery() @see DoCalendarQuery()
*/
function GetEvents( $start = null, $finish = null, $relative_url = null ) {
$filter = "";
if ( isset($start) && isset($finish) ) {
$range = "<C:time-range start=\"$start\" end=\"$finish\"/>";
} else {
$range = '';
}
$filter = <<<EOFILTER
<C:filter>
<C:comp-filter name="VCALENDAR">
<C:comp-filter name="VEVENT">
$range
</C:comp-filter>
</C:comp-filter>
</C:filter>
EOFILTER;
return $this->DoCalendarQuery($filter, $relative_url);
}
/**
* Get the todo's in a range from $start to $finish. The dates should be in the
* format yyyymmddThhmmssZ and should be in GMT. The events are returned as an
* array of event arrays. Each event array will have a 'href', 'etag' and 'event'
* part, where the 'href' is relative to the calendar and the event contains the
* definition of the event in iCalendar format.
*
* @param timestamp $start The start time for the period
* @param timestamp $finish The finish time for the period
* @param boolean $completed Whether to include completed tasks
* @param boolean $cancelled Whether to include cancelled tasks
* @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''.
*
* @return array An array of the relative URLs, etags, and events, returned from DoCalendarQuery() @see DoCalendarQuery()
*/
function GetTodos( $start, $finish, $completed = false, $cancelled = false, $relative_url = null ) {
if ( $start && $finish ) {
$time_range = <<<EOTIME
<C:time-range start="$start" end="$finish"/>
EOTIME;
} else {
$time_range = "";
}
// Warning! May contain traces of double negatives...
$neg_cancelled = ( $cancelled === true ? "no" : "yes" );
$neg_completed = ( $cancelled === true ? "no" : "yes" );
$filter = <<<EOFILTER
<C:filter>
<C:comp-filter name="VCALENDAR">
<C:comp-filter name="VTODO">
<C:prop-filter name="STATUS">
<C:text-match negate-condition="$neg_completed">COMPLETED</C:text-match>
</C:prop-filter>
<C:prop-filter name="STATUS">
<C:text-match negate-condition="$neg_cancelled">CANCELLED</C:text-match>
</C:prop-filter>$time_range
</C:comp-filter>
</C:comp-filter>
</C:filter>
EOFILTER;
return $this->DoCalendarQuery($filter, $relative_url);
}
/**
* Get the calendar entry by UID
*
* @param uid
* @param string $relative_url The URL relative to the base_url specified when the calendar was opened. Default ''.
* @param string $component_type The component type inside the VCALENDAR. Default 'VEVENT'.
*
* @return array An array of the relative URL, etag, and calendar data returned from DoCalendarQuery() @see DoCalendarQuery()
*/
function GetEntryByUid( $uid, $relative_url = null, $component_type = 'VEVENT' ) {
$filter = "";
if ( $uid ) {
$filter = <<<EOFILTER
<C:filter>
<C:comp-filter name="VCALENDAR">
<C:comp-filter name="$component_type">
<C:prop-filter name="UID">
<C:text-match icollation="i;octet">$uid</C:text-match>
</C:prop-filter>
</C:comp-filter>
</C:comp-filter>
</C:filter>
EOFILTER;
}
return $this->DoCalendarQuery($filter, $relative_url);
}
/**
* Get the calendar entry by HREF
*
* @param string $href The href from a call to GetEvents or GetTodos etc.
*
* @return string The iCalendar of the calendar entry
*/
function GetEntryByHref( $href ) {
$href = str_replace( rawurlencode('/'),'/',rawurlencode($href));
return $this->DoGETRequest( $href );
}
/**
* Do a Sync operation. This is the fastest way to detect changes.
*
* @param string $url URL for the calendar
* @param boolean $initial It's the first synchronization
* @param boolean $support_dav_sync The CalDAV server supports sync-collection
*
* @return array of responses
*/
public function GetSync($relative_url = null, $initial = true, $support_dav_sync = false) {
if (!empty($relative_url)) {
$this->SetCalendar($relative_url);
}
if ($support_dav_sync) {
$token = ($initial ? "" : $this->synctoken[$this->calendar_url]);
$body = <<<EOXML
<?xml version="1.0" encoding="utf-8"?>
<D:sync-collection xmlns:D="DAV:">
<D:sync-token>$token</D:sync-token>
<D:sync-level>1</D:sync-level>
<D:prop>
<D:getetag/>
<D:getlastmodified/>
</D:prop>
</D:sync-collection>
EOXML;
}
else {
$body = <<<EOXML
<?xml version="1.0" encoding="utf-8" ?>
<C:calendar-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
<D:prop>
<D:getetag/>
<D:getlastmodified/>
</D:prop>
<C:filter>
<C:comp-filter name="VCALENDAR" />
</C:filter>
</C:calendar-query>
EOXML;
}
$this->SetDepth(1);
$this->DoRequest($this->calendar_url, "REPORT", $body, "text/xml");
$report = array();
foreach( $this->xmlnodes as $k => $v ) {
switch( $v['tag'] ) {
case 'DAV::response':
if ( $v['type'] == 'open' ) {
$response = array();
} elseif ( $v['type'] == 'close' ) {
$report[] = $response;
}
break;
case 'DAV::href':
$response['href'] = basename( rawurldecode($v['value']) );
break;
case 'DAV::getlastmodified':
if (isset($v['value'])) {
$response['getlastmodified'] = $v['value'];
}
else {
$response['getlastmodified'] = '';
}
break;
case 'DAV::getetag':
$response['etag'] = preg_replace('/^"?([^"]+)"?/', '$1', $v['value']);
break;
case 'DAV::sync-token':
$this->synctoken[$this->calendar_url] = $v['value'];
break;
}
}
return $report;
}
}

View file

@ -0,0 +1,892 @@
<?php
/**
* CardDAV PHP
*
* Simple CardDAV query
* --------------------
* $carddav = new carddav_backend('https://davical.example.com/user/contacts/');
* $carddav->set_auth('username', 'password');
* echo $carddav->get();
*
*
* Simple vCard query
* ------------------
* $carddav = new carddav_backend('https://davical.example.com/user/contacts/');
* $carddav->set_auth('username', 'password');
* echo $carddav->get_vcard('0126FFB4-2EB74D0A-302EA17F');
*
*
* XML vCard query
* ------------------
* $carddav = new carddav_backend('https://davical.example.com/user/contacts/');
* $carddav->set_auth('username', 'password');
* echo $carddav->get_xml_vcard('0126FFB4-2EB74D0A-302EA17F');
*
*
* Check CardDAV server connection
* -------------------------------
* $carddav = new carddav_backend('https://davical.example.com/user/contacts/');
* $carddav->set_auth('username', 'password');
* var_dump($carddav->check_connection());
*
*
* CardDAV delete query
* --------------------
* $carddav = new carddav_backend('https://davical.example.com/user/contacts/');
* $carddav->set_auth('username', 'password');
* $carddav->delete('0126FFB4-2EB74D0A-302EA17F');
*
*
* CardDAV add query
* --------------------
* $vcard = 'BEGIN:VCARD
* VERSION:3.0
* UID:1f5ea45f-b28a-4b96-25as-ed4f10edf57b
* FN:Christian Putzke
* N:Christian;Putzke;;;
* EMAIL;TYPE=OTHER:christian.putzke@graviox.de
* END:VCARD';
*
* $carddav = new carddav_backend('https://davical.example.com/user/contacts/');
* $carddav->set_auth('username', 'password');
* $vcard_id = $carddav->add($vcard);
*
*
* CardDAV update query
* --------------------
* $vcard = 'BEGIN:VCARD
* VERSION:3.0
* UID:1f5ea45f-b28a-4b96-25as-ed4f10edf57b
* FN:Christian Putzke
* N:Christian;Putzke;;;
* EMAIL;TYPE=OTHER:christian.putzke@graviox.de
* END:VCARD';
*
* $carddav = new carddav_backend('https://davical.example.com/user/contacts/');
* $carddav->set_auth('username', 'password');
* $carddav->update($vcard, '0126FFB4-2EB74D0A-302EA17F');
*
*
* CardDAV debug
* -------------
* $carddav = new carddav_backend('https://davical.example.com/user/contacts/');
* $carddav->enable_debug();
* $carddav->set_auth('username', 'password');
* $carddav->get();
* var_dump($carddav->get_debug());
*
*
* CardDAV server list
* -------------------
* DAViCal: https://example.com/{resource|principal|username}/{collection}/
* Apple Addressbook Server: https://example.com/addressbooks/users/{resource|principal|username}/{collection}/
* memotoo: https://sync.memotoo.com/cardDAV/
* SabreDAV: https://example.com/addressbooks/{resource|principal|username}/{collection}/
* ownCloud: https://example.com/apps/contacts/carddav.php/addressbooks/{resource|principal|username}/{collection}/
* SOGo: https://example.com/SOGo/dav/{resource|principal|username}/Contacts/{collection}/
*
*
* @author Christian Putzke <christian.putzke@graviox.de>
* @copyright Christian Putzke
* @link http://www.graviox.de/
* @link https://twitter.com/cputzke/
* @since 20.07.2011
* @version 0.6
* @license http://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*
*/
class carddav_backend
{
/**
* CardDAV PHP Version
*
* @constant string
*/
const VERSION = '0.6.c';
/**
* User agent displayed in http requests
*
* @constant string
*/
const USERAGENT = 'Z-Push CardDAV/';
/**
* CardDAV server url
*
* @var string
*/
private $url = null;
/**
* CardDAV server url_parts
*
* @var array
*/
private $url_parts = null;
/**
* Authentication string
*
* @var string
*/
private $auth = null;
/**
* Authentication: username
*
* @var string
*/
private $username = null;
/**
* Authentication: password
*
* @var string
*/
private $password = null;
/**
* Characters used for vCard id generation
*
* @var array
*/
private $vcard_id_chars = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'A', 'B', 'C', 'D', 'E', 'F');
/**
* CardDAV server connection (curl handle)
*
* @var resource
*/
private $curl;
/**
* Debug on or off
*
* @var boolean
*/
private $debug = false;
/**
* All available debug information
*
* @var array
*/
private $debug_information = array();
/**
* Sync-token for sync-collection operations.
*
* @var array[string]
*/
private $synctoken = array();
/* VCard File URL Extension
*
* @var string
*/
private $url_vcard_extension;
/**
* Exception codes
*/
const EXCEPTION_WRONG_HTTP_STATUS_CODE_GET = 1000;
const EXCEPTION_WRONG_HTTP_STATUS_CODE_GET_VCARD = 1001;
const EXCEPTION_WRONG_HTTP_STATUS_CODE_GET_XML_VCARD = 1002;
const EXCEPTION_WRONG_HTTP_STATUS_CODE_DELETE = 1003;
const EXCEPTION_WRONG_HTTP_STATUS_CODE_ADD = 1004;
const EXCEPTION_WRONG_HTTP_STATUS_CODE_UPDATE = 1005;
const EXCEPTION_MALFORMED_XML_RESPONSE = 1006;
const EXCEPTION_COULD_NOT_GENERATE_NEW_VCARD_ID = 1007;
const EXCEPTION_COULD_NOT_FIND_VCARD_HREF = 1008;
/**
* Constructor
* Sets the CardDAV server url
*
* @param string $url CardDAV server url
* @param string $url_vcard_extension extension needed to recover the vcard, it could be empty
*/
public function __construct($url = null, $url_vcard_extension = '.vcf') {
if ($url !== null) {
$this->set_url($url);
}
$this->url_vcard_extension = $url_vcard_extension;
}
/**
* Sets debug information
*
* @param array $debug_information Debug information
* @return void
*/
public function set_debug(array $debug_information) {
$this->debug_information[] = $debug_information;
}
/**
* Sets the CardDAV server url
*
* @param string $url CardDAV server url
* @return void
*/
public function set_url($url) {
$this->url = $url;
// Url always end with trailing /
if (substr($this->url, -1, 1) !== '/') {
$this->url .= '/';
}
$this->url_parts = parse_url($this->url);
}
/**
* Sets authentication information
*
* @param string $username CardDAV server username
* @param string $password CardDAV server password
* @return void
*/
public function set_auth($username, $password) {
$this->username = $username;
$this->password = $password;
$this->auth = $username . ':' . $password;
}
/**
* Gets all available debug information
*
* @return array $this->debug_information All available debug information
*/
public function get_debug() {
return $this->debug_information;
}
/**
* Sets the CardDAV vcard url extension
*
* Most providers do requests handling Vcards with .vcf, however
* this isn't always the case and some providers (such as Google)
* returned a 404 if the .vcf extension is used - or the other
* way around, returning 404 unless .vcf is used.
*
* Both approaches are technically correct, see rfc635
* http://tools.ietf.org/html/rfc6352
*
*
* @param string $extension File extension
* @return void
*/
public function set_vcard_extension($extension) {
$this->url_vcard_extension = $extension;
}
/**
* Gets all vCards including additional information from the CardDAV server.
* This operation could be slow if you have a lot of vcards.
*
* @param boolean $include_vcards Include vCards within the response (simplified only)
* @param boolean $raw Get response raw or simplified
* @params boolean $discover Only discover addressbooks
* @return string Raw or simplified XML response
*/
public function get($include_vcards = true, $raw = false, $discover = false) {
// ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->carddav_backend->get"));
if ($discover) {
$result = $this->query($this->url, 'PROPFIND', null, null, '1');
}
else {
$result = $this->query($this->url, 'PROPFIND');
}
switch ($result['http_code']) {
case 200:
case 207:
if ($raw === true) {
return $result['response'];
}
else {
return $this->simplify($result['response'], $include_vcards);
}
break;
default:
throw new Exception('Woops, something\'s gone wrong! The CardDAV server returned the http status code ' . $result['http_code'] . '.', self::EXCEPTION_WRONG_HTTP_STATUS_CODE_GET);
break;
}
}
/**
* Get all vcards matching a full name or mail.
*
* @param string $pattern Pattern to search
* @param integer $limit Return only N vcards
* @param boolean $include_vcards Include vCards within the response (simplified only)
* @param boolean $raw Get response raw or simplified
* @param boolean $support_fn_search If the server supports searchs by fn
* @return string Raw or simplified XML response
*/
public function search_vcards($pattern, $limit, $include_vcards = true, $raw = false, $support_fn_search = false) {
// ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->carddav_backend->search_vcards"));
if ($support_fn_search) {
$xml = <<<EOFCONTENTSEARCH
<?xml version="1.0" encoding="utf-8" ?>
<C:addressbook-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:carddav">
<D:prop>
<D:getetag/>
<C:address-data>
<C:allprop/>
</C:address-data>
</D:prop>
<C:filter test="anyof">
<C:prop-filter name="FN">
<C:text-match collation="i;unicode-casemap" negate-condition="no" match-type="contains">$pattern</C:text-match>
</C:prop-filter>
</C:filter>
<C:limit>
<C:nresults>$limit</C:nresults>
</C:limit>
</C:addressbook-query>
EOFCONTENTSEARCH;
}
else {
$xml = <<<EOFCONTENTSEARCH
<?xml version="1.0" encoding="utf-8" ?>
<C:addressbook-query xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:carddav">
<D:prop>
<D:getetag/>
<C:address-data>
<C:allprop/>
</C:address-data>
</D:prop>
<C:filter test="anyof">
<C:prop-filter name="sn">
<C:text-match collation="i;unicode-casemap" negate-condition="no" match-type="contains">$pattern</C:text-match>
</C:prop-filter>
<C:prop-filter name="givenname">
<C:text-match collation="i;unicode-casemap" negate-condition="no" match-type="contains">$pattern</C:text-match>
</C:prop-filter>
<C:prop-filter name="email">
<C:text-match collation="i;unicode-casemap" negate-condition="no" match-type="contains">$pattern</C:text-match>
</C:prop-filter>
</C:filter>
<C:limit>
<C:nresults>$limit</C:nresults>
</C:limit>
</C:addressbook-query>
EOFCONTENTSEARCH;
}
return $this->do_query_report($xml, $include_vcards, $raw, true);
}
/**
* Get all vcards or changes since the last sync.
*
* @param boolean $initial If the sync should be full
* @param boolean $include_vcards If the vCards should be included within the response
* @param boolean $support_carddav_sync If the cardDAV server supports sync-collection operations (DAViCal supports it)
* @return string Simplified XML response
*/
public function do_sync($initial = true, $include_vcards = false, $support_carddav_sync = false) {
// ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->carddav_backend->do_sync"));
if ($support_carddav_sync) {
if ($initial) {
$token = "";
}
else {
$token = $this->synctoken[$this->url];
}
$xml = <<<EOFXMLINITIALSYNC
<?xml version="1.0" encoding="utf-8"?>
<D:sync-collection xmlns:D="DAV:">
<D:sync-token>$token</D:sync-token>
<D:sync-level>1</D:sync-level>
<D:prop>
<D:getetag/>
<D:getlastmodified/>
</D:prop>
</D:sync-collection>
EOFXMLINITIALSYNC;
return $this->do_query_report($xml, $include_vcards, false);
}
else {
return $this->get($include_vcards, false);
}
}
/**
* Do a REPORT query against the server
*
* @param string $xml XML body request
* @param boolean $include_vcards If the vCards should be included within the response
* @param boolean $raw If the response should be raw or XML simplified
* @param boolean $remove_duplicates If we will apply uniqness to the response vcards
* @return string
*/
private function do_query_report($xml, $include_vcards = true, $raw = false, $remove_duplicates = false) {
// ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->carddav_backend->do_query_report"));
$result = $this->query($this->url, 'REPORT', $xml, 'text/xml');
try {
switch ($result['http_code']) {
case 200:
case 207:
if ($raw === true) {
return $result['response'];
}
else {
return $this->simplify($result['response'], $include_vcards, $remove_duplicates);
}
break;
default:
throw new Exception('Woops, something\'s gone wrong! The CardDAV server returned the http status code ' . $result['http_code'] . '.', self::EXCEPTION_WRONG_HTTP_STATUS_CODE_GET);
break;
}
}
catch(Exception $ex) {
// vcard not found
if ($ex->getCode() == self::EXCEPTION_COULD_NOT_FIND_VCARD_HREF) {
if (strlen($this->url_vcard_extension) == 0 || stripos($xml, $this->url_vcard_extension) === FALSE) {
throw $ex;
}
else {
// try to do the same without the $this->url_vcard_extension
return $this->do_query_report(str_ireplace($this->url_vcard_extension, "", $xml), $include_vcards, $raw, $remove_duplicates);
}
}
else {
throw $ex;
}
}
}
/**
* Gets a clean vCard from the CardDAV server
*
* @param string $vcard_href vCard href on the CardDAV server
* @return string vCard (text/vcard)
*/
private function get_vcard($vcard_href) {
// ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->carddav_backend->get_vcard"));
$url = $this->url_parts['scheme'] . '://' . $this->url_parts['host'] . ':' . $this->url_parts['port'] . $vcard_href;
$result = $this->query($url, 'GET');
switch ($result['http_code']) {
case 200:
case 207:
return $result['response'];
break;
default:
throw new Exception('Woops, something\'s gone wrong! The CardDAV server returned the http status code ' . $result['http_code'] . '.', self::EXCEPTION_WRONG_HTTP_STATUS_CODE_GET_VCARD);
break;
}
}
/**
* Gets a vCard + XML from the CardDAV Server
*
* @param string $vcard_id vCard id on the CardDAV Server
* @return string Raw or simplified vCard (text/xml)
*/
public function get_xml_vcard($vcard_id) {
// ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->carddav_backend->get_xml_vcard"));
$href = $this->url_parts['path'] . str_replace($this->url_vcard_extension, null, $vcard_id) . $this->url_vcard_extension;
// If we don't ask for allprop, SOGo doesn't return the content_type
$xml = <<<EOFXMLGETXMLVCARD
<?xml version="1.0" encoding="utf-8" ?>
<C:addressbook-multiget xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:carddav">
<D:prop>
<D:getetag/>
<D:getlastmodified/>
<C:address-data>
<C:allprop/>
</C:address-data>
</D:prop>
<D:href>$href</D:href>
</C:addressbook-multiget>
EOFXMLGETXMLVCARD;
return $this->do_query_report($xml);
}
/**
* Enables the debug mode
*
* @return void
*/
public function enable_debug() {
$this->debug = true;
}
/**
* Checks if the CardDAV server is reachable
*
* @return boolean
*/
public function check_connection() {
// ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->carddav_backend->check_connection"));
$result = $this->query($this->url, 'OPTIONS');
$status = false;
switch($result['http_code']) {
case 200:
case 207:
case 401:
$status = true;
break;
}
return $status;
}
/**
* Deletes an entry from the CardDAV server
*
* @param string $vcard_id vCard id on the CardDAV server
* @return boolean
*/
public function delete($vcard_id) {
// ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->carddav_backend->delete"));
$result = $this->query($this->url . $vcard_id . $this->url_vcard_extension, 'DELETE');
switch ($result['http_code']) {
case 204:
return true;
break;
default:
throw new Exception('Woops, something\'s gone wrong! The CardDAV server returned the http status code ' . $result['http_code'] . '.', self::EXCEPTION_WRONG_HTTP_STATUS_CODE_DELETE);
break;
}
}
/**
* Adds an entry to the CardDAV server
*
* @param string $vcard vCard
* @param string $vcard_id vCard id on the CardDAV server
* @return string The new vCard id
*/
public function add($vcard, $vcard_id = null) {
// ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->carddav_backend->add"));
if ($vcard_id === null) {
$vcard_id = $this->generate_vcard_id();
}
$vcard = str_replace("\nEND:VCARD","\nUID:" . $vcard_id . "\r\nEND:VCARD", $vcard);
$result = $this->query($this->url . $vcard_id . $this->url_vcard_extension, 'PUT', $vcard, 'text/vcard');
switch($result['http_code']) {
case 201:
case 204:
return $vcard_id;
break;
default:
throw new Exception('Woops, something\'s gone wrong! The CardDAV server returned the http status code ' . $result['http_code'] . '.', self::EXCEPTION_WRONG_HTTP_STATUS_CODE_ADD);
break;
}
}
/**
* Updates an entry to the CardDAV server
*
* @param string $vcard vCard
* @param string $vcard_id vCard id on the CardDAV server
* @return boolean
*/
public function update($vcard, $vcard_id) {
try {
return $this->add($vcard, $vcard_id);
}
catch (Exception $e) {
throw new Exception($e->getMessage(), self::EXCEPTION_WRONG_HTTP_STATUS_CODE_UPDATE);
}
}
/**
* Simplify CardDAV XML response
*
* @param string $response CardDAV XML response
* @param boolean $include_vcards Include vCards or not
* @param boolean $remove_duplicates If we will apply uniqness to the response vcards
* @return string Simplified CardDAV XML response
*/
private function simplify($response, $include_vcards = true, $remove_duplicates = false) {
$response = $this->remove_namespaces($response);
try {
$xml = new SimpleXMLElement($response);
}
catch(Exception $e) {
throw new Exception('The XML response seems to be malformed and can\'t be simplified!', self::EXCEPTION_MALFORMED_XML_RESPONSE, $e);
}
if (!empty($xml->{'sync-token'})) {
$this->synctoken[$this->url] = $xml->{'sync-token'};
}
$simplified_xml = new XMLWriter();
$simplified_xml->openMemory();
$simplified_xml->setIndent(4);
$simplified_xml->startDocument('1.0', 'utf-8');
$simplified_xml->startElement('response');
if (!empty($xml->response)) {
$unique_etags = array();
foreach ($xml->response as $response) {
if (isset($response->propstat)) {
if ((strlen($this->url_vcard_extension) > 0 && preg_match('/'.$this->url_vcard_extension.'/', $response->href) &&
!(isset($response->propstat->prop->resourcetype) && isset($response->propstat->prop->resourcetype->addressbook)))
|| preg_match('/vcard/', $response->propstat->prop->getcontenttype) || isset($response->propstat->prop->{'address-data'}) || isset($response->propstat->prop->{'addressbook-data'})) {
// It's a vcard
$id = basename($response->href);
$id = str_replace($this->url_vcard_extension, null, $id);
if (!empty($id)) {
$simplified_xml->startElement('element');
$simplified_xml->writeElement('id', $id);
$simplified_xml->writeElement('etag', str_replace('"', null, $response->propstat->prop->getetag));
$simplified_xml->writeElement('last_modified', $response->propstat->prop->getlastmodified);
if ($include_vcards === true) {
if (isset($response->propstat->prop->{'address-data'})) {
// We already have the full vcard
$simplified_xml->writeElement('vcard', $response->propstat->prop->{'address-data'});
}
else if (isset($response->propstat->prop->{'addressbook-data'})) {
// We already have the full vcard, also
$simplified_xml->writeElement('vcard', $response->propstat->prop->{'addressbook-data'});
}
else {
// We don't have the vcard, we need to get it. We never should hit here, it would mean a buggy server
$simplified_xml->writeElement('vcard', $this->get_vcard($response->href));
}
}
$simplified_xml->endElement();
}
}
else if (isset($response->propstat->prop->resourcetype->addressbook)) {
// It's an addressbook
if (isset($response->propstat->prop->href)) {
$href = $response->propstat->prop->href;
}
else if (isset($response->href)) {
$href = $response->href;
}
else {
$href = null;
}
$url = str_replace($this->url_parts['path'], null, $this->url) . $href;
$simplified_xml->startElement('addressbook_element');
$simplified_xml->writeElement('display_name', $response->propstat->prop->displayname);
$simplified_xml->writeElement('url', $url);
$simplified_xml->writeElement('last_modified', $response->propstat->prop->getlastmodified);
$simplified_xml->endElement();
}
}
else {
// We don't have a propstat node, so it will be an error answer
if (isset($response->status) && preg_match('/404 Not Found/', $response->status)) {
throw new Exception('Not found!', self::EXCEPTION_COULD_NOT_FIND_VCARD_HREF);
}
else {
throw new Exception('The XML response is an error message and can\'t be simplified!', self::EXCEPTION_MALFORMED_XML_RESPONSE);
}
}
}
unset($unique_etags);
}
$simplified_xml->endElement();
$simplified_xml->endDocument();
return $simplified_xml->outputMemory();
}
/**
* Cleans CardDAV XML response
*
* @param string $response CardDAV XML response
* @return string $response Cleaned CardDAV XML response
*/
private function remove_namespaces($response) {
// $response = preg_replace('/<[a-z0-9]+:(.*)/i', '<$1', $response);
// $response = preg_replace('/<\/[a-z0-9]+:(.*)/i', '</$1', $response);
// Removing namespace it's pretty hard with regex.
// Also, each server uses different namespaces, so using namespaces it's impossible
// This uses DOMDocument, so it won't be the fastest or least memory-using way.
// Feel free to suggest or improve it
// http://stackoverflow.com/questions/15634291/remove-name-space-from-xml-file-and-save-as-new-xml
$xsl = <<<EOFXSL
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:output method="xml" version="1.0" encoding="UTF-8" />
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="@* | node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
EOFXSL;
$dom = new DOMDocument();
$dom->loadXML($response);
$stylesheet = new DOMDocument();
$stylesheet->loadXML($xsl);
$xsltprocessor = new XSLTProcessor();
$xsltprocessor->importStylesheet($stylesheet);
$response = $xsltprocessor->transformToXML($dom);
$dom = null;
$stylesheet = null;
$xsltprocessor = null;
return $response;
}
/**
* Curl initialization
*
* @return void
*/
public function curl_init() {
if (empty($this->curl)) {
$this->curl = curl_init();
curl_setopt($this->curl, CURLOPT_HEADER, true);
curl_setopt($this->curl, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($this->curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($this->curl, CURLOPT_USERAGENT, self::USERAGENT.self::VERSION);
if ($this->auth !== null) {
curl_setopt($this->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
curl_setopt($this->curl, CURLOPT_USERPWD, $this->auth);
}
}
}
/**
* Query the CardDAV server via curl and returns the response
*
* @param string $url CardDAV server URL
* @param string $method HTTP method like (OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, COPY, MOVE)
* @param string $content Content for CardDAV queries
* @param string $content_type Set content type
* @param string $depth Set Depth
* @return array Raw CardDAV Response and http status code
*/
private function query($url, $method, $content = null, $content_type = null, $depth = "infinity") {
// ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCardDAV->carddav_backend->query - '%s' '%s' '%s' '%s'", $url, $method, $content, $content_type));
$this->curl_init();
curl_setopt($this->curl, CURLOPT_URL, $url);
curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $method);
if ($content !== null) {
curl_setopt($this->curl, CURLOPT_POST, true);
curl_setopt($this->curl, CURLOPT_POSTFIELDS, $content);
}
else {
curl_setopt($this->curl, CURLOPT_POST, false);
curl_setopt($this->curl, CURLOPT_POSTFIELDS, null);
}
if ($content_type !== null) {
curl_setopt($this->curl, CURLOPT_HTTPHEADER, array('Content-type: '.$content_type. '; charset=utf-8', 'Depth: '.$depth));
}
else {
curl_setopt($this->curl, CURLOPT_HTTPHEADER, array('Depth: '.$depth));
}
$complete_response = curl_exec($this->curl);
$header_size = curl_getinfo($this->curl, CURLINFO_HEADER_SIZE);
$http_code = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
$header = trim(substr($complete_response, 0, $header_size));
$response = substr($complete_response, $header_size);
$return = array(
'response' => $response,
'http_code' => $http_code
);
if ($this->debug === true) {
$debug = $return;
$debug['url'] = $url;
$debug['method'] = $method;
$debug['content'] = $content;
$debug['content_type'] = $content_type;
$debug['header'] = $header;
$this->set_debug($debug);
}
return $return;
}
/**
* Returns a valid and unused vCard id
*
* @return string $vcard_id Valid vCard id
*/
private function generate_vcard_id() {
$vcard_id = null;
for ($number = 0; $number <= 25; $number ++) {
if ($number == 8 || $number == 17) {
$vcard_id .= '-';
}
else {
$vcard_id .= $this->vcard_id_chars[mt_rand(0, (count($this->vcard_id_chars) - 1))];
}
}
try {
$carddav = new carddav_backend($this->url);
$carddav->set_auth($this->username, $this->password);
$result = $carddav->query($this->url . $vcard_id . $this->url_vcard_extension, 'GET');
if ($result['http_code'] !== 404) {
$vcard_id = $this->generate_vcard_id();
}
return $vcard_id;
}
catch (Exception $e) {
throw new Exception($e->getMessage(), self::EXCEPTION_COULD_NOT_GENERATE_NEW_VCARD_ID);
}
}
/**
* Destructor
* Close curl connection if it's open
*
* @return void
*/
public function __destruct() {
if (!empty($this->curl)) {
curl_close($this->curl);
}
}
}

View file

@ -133,15 +133,24 @@ include_once('version.php');
Request::Initialize(); Request::Initialize();
ZLog::Initialize(); ZLog::Initialize();
$autenticationInfo = Request::AuthenticationInfo();
$GETUser = Request::GetGETUser();
ZLog::Write(LOGLEVEL_DEBUG,"-------- Start"); ZLog::Write(LOGLEVEL_DEBUG,"-------- Start");
ZLog::Write(LOGLEVEL_INFO, ZLog::Write(LOGLEVEL_INFO,
sprintf("Version='%s' method='%s' from='%s' cmd='%s' getUser='%s' devId='%s' devType='%s'", sprintf("Version='%s' method='%s' from='%s' cmd='%s' getUser='%s' devId='%s' devType='%s'",
@constant('ZPUSH_VERSION'), Request::GetMethod(), Request::GetRemoteAddr(), @constant('ZPUSH_VERSION'), Request::GetMethod(), Request::GetRemoteAddr(),
Request::GetCommand(), Request::GetGETUser(), Request::GetDeviceID(), Request::GetDeviceType())); Request::GetCommand(), $GETUser, Request::GetDeviceID(), Request::GetDeviceType()));
// Stop here if this is an OPTIONS request // Stop here if this is an OPTIONS request
if (Request::IsMethodOPTIONS()) if (Request::IsMethodOPTIONS()) {
throw new NoPostRequestException("Options request", NoPostRequestException::OPTIONS_REQUEST); if (!$autenticationInfo || !$GETUser) {
throw new AuthenticationRequiredException("Access denied. Please send authorisation information");
}
else {
throw new NoPostRequestException("Options request", NoPostRequestException::OPTIONS_REQUEST);
}
}
ZPush::CheckAdvancedConfig(); ZPush::CheckAdvancedConfig();
@ -152,11 +161,22 @@ include_once('version.php');
if(Request::IsMethodPOST() && (Request::GetCommandCode() === false || !Request::GetDeviceID() || !Request::GetDeviceType())) if(Request::IsMethodPOST() && (Request::GetCommandCode() === false || !Request::GetDeviceID() || !Request::GetDeviceType()))
throw new FatalException("Requested the Z-Push URL without the required GET parameters"); throw new FatalException("Requested the Z-Push URL without the required GET parameters");
// This won't be useful with Zarafa, but it will be with standalone Z-Push
if (defined('PRE_AUTHORIZE_USERS') && PRE_AUTHORIZE_USERS === true) {
if (!Request::IsMethodGET()) {
// Check if User/Device are authorized
if (ZPush::GetDeviceManager()->GetUserDevicePermission($GETUser, Request::GetDeviceID()) != SYNC_COMMONSTATUS_SUCCESS) {
throw new AuthenticationRequiredException("Access denied. Username and Device not authorized");
}
}
}
// Load the backend // Load the backend
$backend = ZPush::GetBackend(); $backend = ZPush::GetBackend();
// always request the authorization header // always request the authorization header
if (! Request::AuthenticationInfo() || !Request::GetGETUser()) if (!$autenticationInfo || !$GETUser)
throw new AuthenticationRequiredException("Access denied. Please send authorisation information"); throw new AuthenticationRequiredException("Access denied. Please send authorisation information");
// check the provisioning information // check the provisioning information

View file

@ -67,6 +67,7 @@ class ASDevice extends StateObject {
'asversion' => false, 'asversion' => false,
'ignoredmessages' => array(), 'ignoredmessages' => array(),
'announcedASversion' => false, 'announcedASversion' => false,
'foldersynccomplete' => true,
); );
static private $loadedData; static private $loadedData;

View file

@ -115,6 +115,7 @@ class ChangesMemoryWrapper extends HierarchyCache implements IImportChanges, IEx
public function LoadConflicts($contentparameters, $state) { return true; } public function LoadConflicts($contentparameters, $state) { return true; }
public function ConfigContentParameters($contentparameters) { return true; } public function ConfigContentParameters($contentparameters) { return true; }
public function ImportMessageReadFlag($id, $flags) { return true; } public function ImportMessageReadFlag($id, $flags) { return true; }
public function ImportMessageStarFlag($id, $flags) { return true; }
public function ImportMessageMove($id, $newfolder) { return true; } public function ImportMessageMove($id, $newfolder) { return true; }
/**---------------------------------------------------------------------------------------------------------- /**----------------------------------------------------------------------------------------------------------

View file

@ -595,6 +595,11 @@ class DeviceManager {
* @return boolean * @return boolean
*/ */
public function IsHierarchyFullResyncRequired() { public function IsHierarchyFullResyncRequired() {
// do not check for loop detection, if the foldersync is not yet complete
if ($this->GetFolderSyncComplete() === false) {
ZLog::Write(LOGLEVEL_INFO, "DeviceManager->IsHierarchyFullResyncRequired(): aborted, as exporting of folders has not yet completed");
return false;
}
// check for potential process loops like described in ZP-5 // check for potential process loops like described in ZP-5
return $this->loopdetection->ProcessLoopDetectionIsHierarchyResyncRequired(); return $this->loopdetection->ProcessLoopDetectionIsHierarchyResyncRequired();
} }
@ -695,6 +700,45 @@ class DeviceManager {
return true; return true;
} }
/**
* Returns the indicator if the FolderSync was completed successfully (all folders synchronized)
*
* @access public
* @return boolean
*/
public function GetFolderSyncComplete() {
return $this->device->GetFolderSyncComplete();
}
/**
* Sets if the FolderSync was completed successfully (all folders synchronized)
*
* @param boolean $complete indicating if all folders were sent
*
* @access public
* @return boolean
*/
public function SetFolderSyncComplete($complete, $user = false, $devid = false) {
$this->device->SetFolderSyncComplete($complete);
}
/**
* Removes the Loop detection data for a user & device
*
* @param string $user
* @param string $devid
*
* @access public
* @return boolean
*/
public function ClearLoopDetectionData($user, $devid) {
if ($user == false || $devid == false) {
return false;
}
ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->ClearLoopDetectionData(): clearing data for user '%s' and device '%s'", $user, $devid));
return $this->loopdetection->ClearData($user, $devid);
}
/** /**
* Indicates if the device needs an AS version update * Indicates if the device needs an AS version update
* *
@ -709,6 +753,24 @@ class DeviceManager {
return ($announced != $latest); return ($announced != $latest);
} }
/**----------------------------------------------------------------------------------------------------------
* DeviceManager User-Device pre-authorization
*/
/**
* Return if the User-Device has permission to sync against this Z-Push.
*
* @param string $user Username
* @param string $devid DeviceId
*
* @access public
* @return integer
*/
public function GetUserDevicePermission($user, $devid) {
return $this->statemachine->GetUserDevicePermission($user, $devid);
}
/**---------------------------------------------------------------------------------------------------------- /**----------------------------------------------------------------------------------------------------------
* private DeviceManager methods * private DeviceManager methods
*/ */

View file

@ -294,4 +294,4 @@ abstract class InterProcessData {
} }
?> ?>

View file

@ -189,6 +189,41 @@ class ImportChangesStream implements IImportChanges {
return true; return true;
} }
/**
* Imports a change in 'star' flag
* Can only be applied to SyncMail (Email) requests
*
* @param string $id
* @param int $flags - flagged/unflagged
*
* @access public
* @return boolean
*/
public function ImportMessageStarFlag($id, $flags) {
if(!($this->objclass instanceof SyncMail))
return false;
$this->importedMsgs++;
$this->encoder->startTag(SYNC_MODIFY);
$this->encoder->startTag(SYNC_SERVERENTRYID);
$this->encoder->content($id);
$this->encoder->endTag();
$this->encoder->startTag(SYNC_DATA);
$this->encoder->startTag(SYNC_POOMMAIL_FLAG);
$this->encoder->startTag(SYNC_POOMMAIL_FLAGSTATUS);
$this->encoder->content($flags == 1? "2" : "0");
$this->encoder->endTag();
$this->encoder->startTag(SYNC_POOMMAIL_FLAGTYPE);
$this->encoder->content("FollowUp");
$this->encoder->endTag();
$this->encoder->endTag();
$this->encoder->endTag();
$this->encoder->endTag();
return true;
}
/** /**
* ImportMessageMove is not implemented, as this operation can not be streamed to a WBXMLEncoder * ImportMessageMove is not implemented, as this operation can not be streamed to a WBXMLEncoder
* *

View file

@ -449,6 +449,13 @@ class SyncCollections implements Iterator {
// wait for changes // wait for changes
$started = time(); $started = time();
$endat = time() + $lifetime; $endat = time() + $lifetime;
// always use policy key from the request if it was sent
$policyKey = $this->GetReferencePolicyKey();
if (Request::WasPolicyKeySent() && Request::GetPolicyKey() != 0) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("refpolkey:'%s', sent polkey:'%s'", $policyKey, Request::GetPolicyKey()));
$policyKey = Request::GetPolicyKey();
}
while(($now = time()) < $endat) { while(($now = time()) < $endat) {
// how long are we waiting for changes // how long are we waiting for changes
$this->waitingTime = $now-$started; $this->waitingTime = $now-$started;
@ -460,7 +467,7 @@ class SyncCollections implements Iterator {
// Check if provisioning is necessary // Check if provisioning is necessary
// if a PolicyKey was sent use it. If not, compare with the ReferencePolicyKey // if a PolicyKey was sent use it. If not, compare with the ReferencePolicyKey
if (PROVISIONING === true && $this->GetReferencePolicyKey() !== false && ZPush::GetDeviceManager()->ProvisioningRequired($this->GetReferencePolicyKey(), true)) if (PROVISIONING === true && $policyKey !== false && ZPush::GetDeviceManager()->ProvisioningRequired($policyKey, true))
// the hierarchysync forces provisioning // the hierarchysync forces provisioning
throw new StatusException("SyncCollections->CheckForChanges(): PolicyKey changed. Provisioning required.", self::ERROR_WRONG_HIERARCHY); throw new StatusException("SyncCollections->CheckForChanges(): PolicyKey changed. Provisioning required.", self::ERROR_WRONG_HIERARCHY);

View file

@ -147,7 +147,7 @@ class ZPush {
self::COMMAND_SETTINGS => array(self::ASV_12, self::REQUESTHANDLER => "Settings"), self::COMMAND_SETTINGS => array(self::ASV_12, self::REQUESTHANDLER => "Settings"),
self::COMMAND_WEBSERVICE_DEVICE => array(self::REQUESTHANDLER => "Webservice", self::PLAININPUT, self::NOACTIVESYNCCOMMAND, self::WEBSERVICECOMMAND), self::COMMAND_WEBSERVICE_DEVICE => array(self::REQUESTHANDLER => "Webservice", self::PLAININPUT, self::NOACTIVESYNCCOMMAND, self::WEBSERVICECOMMAND),
self::COMMAND_WEBSERVICE_USERS => array(self::REQUESTHANDLER => "Webservice", self::PLAININPUT, self::NOACTIVESYNCCOMMAND, self::WEBSERVICECOMMAND), self::COMMAND_WEBSERVICE_USERS => array(self::REQUESTHANDLER => "Webservice", self::PLAININPUT, self::NOACTIVESYNCCOMMAND, self::WEBSERVICECOMMAND),
); );
@ -276,12 +276,16 @@ class ZPush {
else if (SINK_FORCERECHECK !== false && (!is_int(SINK_FORCERECHECK) || SINK_FORCERECHECK < 1)) else if (SINK_FORCERECHECK !== false && (!is_int(SINK_FORCERECHECK) || SINK_FORCERECHECK < 1))
throw new FatalMisconfigurationException("The SINK_FORCERECHECK value must be 'false' or a number higher than 0."); throw new FatalMisconfigurationException("The SINK_FORCERECHECK value must be 'false' or a number higher than 0.");
if (!defined('SYNC_CONTACTS_MAXPICTURESIZE')) { if (!defined('SYNC_CONTACTS_MAXPICTURESIZE')) {
define('SYNC_CONTACTS_MAXPICTURESIZE', 49152); define('SYNC_CONTACTS_MAXPICTURESIZE', 49152);
} }
else if ((!is_int(SYNC_CONTACTS_MAXPICTURESIZE) || SYNC_CONTACTS_MAXPICTURESIZE < 1)) else if ((!is_int(SYNC_CONTACTS_MAXPICTURESIZE) || SYNC_CONTACTS_MAXPICTURESIZE < 1))
throw new FatalMisconfigurationException("The SYNC_CONTACTS_MAXPICTURESIZE value must be a number higher than 0."); throw new FatalMisconfigurationException("The SYNC_CONTACTS_MAXPICTURESIZE value must be a number higher than 0.");
if (!defined('USE_PARTIAL_FOLDERSYNC')) {
define('USE_PARTIAL_FOLDERSYNC', false);
}
// the check on additional folders will not throw hard errors, as this is probably changed on live systems // the check on additional folders will not throw hard errors, as this is probably changed on live systems
if (isset($additionalFolders) && !is_array($additionalFolders)) if (isset($additionalFolders) && !is_array($additionalFolders))
ZLog::Write(LOGLEVEL_ERROR, "ZPush::CheckConfig() : The additional folders synchronization not available as array."); ZLog::Write(LOGLEVEL_ERROR, "ZPush::CheckConfig() : The additional folders synchronization not available as array.");
@ -349,8 +353,14 @@ class ZPush {
} }
else { else {
// Initialize the default StateMachine // Initialize the default StateMachine
include_once('lib/default/filestatemachine.php'); if (defined('STATE_MACHINE') && STATE_MACHINE == 'SQL') {
ZPush::$stateMachine = new FileStateMachine(); include_once('lib/default/sqlstatemachine.php');
ZPush::$stateMachine = new SqlStateMachine();
}
else {
include_once('lib/default/filestatemachine.php');
ZPush::$stateMachine = new FileStateMachine();
}
} }
if (ZPush::$stateMachine->GetStateVersion() !== ZPush::GetLatestStateVersion()) { if (ZPush::$stateMachine->GetStateVersion() !== ZPush::GetLatestStateVersion()) {
@ -617,9 +627,9 @@ class ZPush {
$message $additionalMessage $message $additionalMessage
<br><br> <br><br>
More information about Z-Push can be found at:<br> More information about Z-Push can be found at:<br>
<a href="http://z-push.sf.net/">Z-Push homepage</a><br> <a href="http://z-push.org/">Z-Push homepage</a><br>
<a href="http://z-push.sf.net/download">Z-Push download page at BerliOS</a><br> <a href="http://z-push.org/download">Z-Push download page</a><br>
<a href="http://z-push.sf.net/tracker">Z-Push Bugtracker and Roadmap</a><br> <a href="http://jira.zarafa.com/browse/ZP">Z-Push Bugtracker and Roadmap</a><br>
<br> <br>
All modifications to this sourcecode must be published and returned to the community.<br> All modifications to this sourcecode must be published and returned to the community.<br>
Please see <a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPLv3 License</a> for details.<br> Please see <a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPLv3 License</a> for details.<br>
@ -676,7 +686,9 @@ END;
* @return string * @return string
*/ */
static public function GetSupportedProtocolVersions($valueOnly = false) { static public function GetSupportedProtocolVersions($valueOnly = false) {
$versions = implode(',', array_slice(self::$supportedASVersions, 0, (array_search(self::GetSupportedASVersion(), self::$supportedASVersions)+1))); //$versions = implode(',', array_slice(self::$supportedASVersions, 0, (array_search(self::GetSupportedASVersion(), self::$supportedASVersions)+1)));
// Removing support for AS 1.0, 2.0, 2.1 - That will make Outlook 2013 works
$versions = implode(',', array_slice(self::$supportedASVersions, 3, (array_search(self::GetSupportedASVersion(), self::$supportedASVersions)+1)));
ZLog::Write(LOGLEVEL_DEBUG, "ZPush::GetSupportedProtocolVersions(): " . $versions); ZLog::Write(LOGLEVEL_DEBUG, "ZPush::GetSupportedProtocolVersions(): " . $versions);
if ($valueOnly === true) if ($valueOnly === true)

View file

@ -1025,6 +1025,7 @@ define("SYNC_FLAGSTATUS_COMPLETE", 1);
define("SYNC_FLAGSTATUS_ACTIVE", 2); define("SYNC_FLAGSTATUS_ACTIVE", 2);
define("DEFAULT_EMAIL_CONTENTCLASS", "urn:content-classes:message"); define("DEFAULT_EMAIL_CONTENTCLASS", "urn:content-classes:message");
define("DEFAULT_CALENDAR_CONTENTCLASS", "urn:content-classes:calendarmessage");
define("SYNC_MAIL_LASTVERB_UNKNOWN", 0); define("SYNC_MAIL_LASTVERB_UNKNOWN", 0);
define("SYNC_MAIL_LASTVERB_REPLYSENDER", 1); define("SYNC_MAIL_LASTVERB_REPLYSENDER", 1);

View file

@ -203,6 +203,17 @@ abstract class Backend implements IBackend {
return $r; return $r;
} }
/**
* Returns the email address and the display name of the user. Used by autodiscover.
*
* @param string $username The username
*
* @access public
* @return Array
*/
public function GetUserDetails($username) {
return array('emailaddress' => $username, 'fullname' => $username);
}
/**---------------------------------------------------------------------------------------------------------- /**----------------------------------------------------------------------------------------------------------
* Protected methods for BackendStorage * Protected methods for BackendStorage

View file

@ -340,6 +340,25 @@ abstract class BackendDiff extends Backend {
*/ */
public abstract function SetReadFlag($folderid, $id, $flags, $contentParameters); public abstract function SetReadFlag($folderid, $id, $flags, $contentParameters);
/**
* Changes the 'star' flag of a message on disk. The $flags
* parameter can only be '2' (starred) or '0' (unstarred). After a call to
* SetStarFlag(), GetMessageList() should return the message with the
* new 'flags' but should not modify the 'mod' parameter. If you do
* change 'mod', simply setting the message to 'starred' on the mobile will trigger
* a full resync of the item from the server.
*
* @param string $folderid id of the folder
* @param string $id id of the message
* @param int $flags star flag of the message
* @param ContentParameters $contentParameters
*
* @access public
* @return boolean status of the operation
* @throws StatusException could throw specific SYNC_STATUS_* exceptions
*/
public abstract function SetStarFlag($folderid, $id, $flags, $contentParameters);
/** /**
* Called when the user has requested to delete (really delete) a message. Usually * Called when the user has requested to delete (really delete) a message. Usually
* this means just unlinking the file its in or somesuch. After this call has succeeded, a call to * this means just unlinking the file its in or somesuch. After this call has succeeded, a call to

View file

@ -88,7 +88,19 @@ class DiffState implements IChanges {
*/ */
public function ConfigContentParameters($contentparameters) { public function ConfigContentParameters($contentparameters) {
$this->contentparameters = $contentparameters; $this->contentparameters = $contentparameters;
$this->cutoffdate = Utils::GetCutOffDate($contentparameters->GetFilterType());
$filtertype = $contentparameters->GetFilterType();
switch($contentparameters->GetContentClass()) {
case "Email":
case "Calendar":
$this->cutoffdate = ($filtertype === false) ? 0 : Utils::GetCutOffDate($filtertype);
break;
case "Contacts":
case "Tasks":
default:
$this->cutoffdate = 0;
break;
}
} }
/** /**
@ -120,8 +132,12 @@ class DiffState implements IChanges {
* @return boolean * @return boolean
*/ */
static public function RowCmp($a, $b) { static public function RowCmp($a, $b) {
// TODO implement different comparing functions if (is_numeric($a["id"]) && is_numeric($b["id"])) {
return $a["id"] < $b["id"] ? 1 : -1; return $a["id"] < $b["id"] ? 1 : -1;
}
else {
return strcmp($a["id"], $b["id"]) < 0 ? 1 : -1;
}
} }
/** /**
@ -152,7 +168,7 @@ class DiffState implements IChanges {
break; break;
if($this->syncstate[$iold]["id"] == $new[$inew]["id"]) { if($this->syncstate[$iold]["id"] == $new[$inew]["id"]) {
// Both messages are still available, compare flags and mod // Both messages are still available, compare flags, star and mod
if(isset($this->syncstate[$iold]["flags"]) && isset($new[$inew]["flags"]) && $this->syncstate[$iold]["flags"] != $new[$inew]["flags"]) { if(isset($this->syncstate[$iold]["flags"]) && isset($new[$inew]["flags"]) && $this->syncstate[$iold]["flags"] != $new[$inew]["flags"]) {
// Flags changed // Flags changed
$change["type"] = "flags"; $change["type"] = "flags";
@ -161,6 +177,14 @@ class DiffState implements IChanges {
$changes[] = $change; $changes[] = $change;
} }
if(isset($this->syncstate[$iold]["star"]) && isset($new[$inew]["star"]) && $this->syncstate[$iold]["star"] != $new[$inew]["star"]) {
// Star changed
$change["type"] = "star";
$change["id"] = $new[$inew]["id"];
$change["star"] = $new[$inew]["star"];
$changes[] = $change;
}
if($this->syncstate[$iold]["mod"] != $new[$inew]["mod"]) { if($this->syncstate[$iold]["mod"] != $new[$inew]["mod"]) {
$change["type"] = "change"; $change["type"] = "change";
$change["id"] = $new[$inew]["id"]; $change["id"] = $new[$inew]["id"];
@ -180,6 +204,7 @@ class DiffState implements IChanges {
// Message in new seems to be new (add) // Message in new seems to be new (add)
$change["type"] = "change"; $change["type"] = "change";
$change["flags"] = SYNC_NEWMESSAGE; $change["flags"] = SYNC_NEWMESSAGE;
$change["star"] = SYNC_NEWMESSAGE;
$change["id"] = $new[$inew]["id"]; $change["id"] = $new[$inew]["id"];
$changes[] = $change; $changes[] = $change;
$inew++; $inew++;
@ -199,6 +224,7 @@ class DiffState implements IChanges {
// All data left in new have been added // All data left in new have been added
$change["type"] = "change"; $change["type"] = "change";
$change["flags"] = SYNC_NEWMESSAGE; $change["flags"] = SYNC_NEWMESSAGE;
$change["star"] = SYNC_NEWMESSAGE;
$change["id"] = $new[$inew]["id"]; $change["id"] = $new[$inew]["id"];
$changes[] = $change; $changes[] = $change;
$inew++; $inew++;
@ -235,6 +261,9 @@ class DiffState implements IChanges {
if($type == "flags") { if($type == "flags") {
// Update flags // Update flags
$this->syncstate[$i]["flags"] = $change["flags"]; $this->syncstate[$i]["flags"] = $change["flags"];
} else if($type == "star") {
// Update star
$this->syncstate[$i]["star"] = $change["star"];
} else if($type == "delete") { } else if($type == "delete") {
// Delete item // Delete item
array_splice($this->syncstate, $i, 1); array_splice($this->syncstate, $i, 1);

View file

@ -195,6 +195,10 @@ class ExportChangesDiff extends DiffState implements IExportChanges{
if($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportMessageReadFlag($change["id"], $change["flags"]) == true) if($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportMessageReadFlag($change["id"], $change["flags"]) == true)
$this->updateState("flags", $change); $this->updateState("flags", $change);
break; break;
case "star":
if($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportMessageStarFlag($change["id"], $change["star"]) == true)
$this->updateState("star", $change);
break;
case "move": case "move":
if($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportMessageMove($change["id"], $change["parent"]) == true) if($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportMessageMove($change["id"], $change["parent"]) == true)
$this->updateState("move", $change); $this->updateState("move", $change);

View file

@ -100,6 +100,7 @@ class ImportChangesDiff extends DiffState implements IImportChanges {
$change["mod"] = 0; // dummy, will be updated later if the change succeeds $change["mod"] = 0; // dummy, will be updated later if the change succeeds
$change["parent"] = $this->folderid; $change["parent"] = $this->folderid;
$change["flags"] = (isset($message->read)) ? $message->read : 0; $change["flags"] = (isset($message->read)) ? $message->read : 0;
$change["star"] = (isset($message->flag) && isset($message->flag->flagstatus)) ? $message->flag->flagstatus : 0;
$this->updateState("change", $change); $this->updateState("change", $change);
if($conflict && $this->flags == SYNC_CONFLICT_OVERWRITE_PIM) if($conflict && $this->flags == SYNC_CONFLICT_OVERWRITE_PIM)
@ -184,6 +185,35 @@ class ImportChangesDiff extends DiffState implements IImportChanges {
return true; return true;
} }
/**
* Imports a change in 'star' flag
* This can never conflict
*
* @param string $id
* @param int $flags - flagged/unflagged
*
* @access public
* @return boolean
* @throws StatusException
*/
public function ImportMessageStarFlag($id, $flags) {
//do nothing if it is a dummy folder
if ($this->folderid == SYNC_FOLDER_TYPE_DUMMY)
throw new StatusException(sprintf("ImportChangesDiff->ImportMessageStarFlag('%s','%s'): can not be done on a dummy folder", $id, $flags), SYNC_STATUS_SYNCCANNOTBECOMPLETED);
// Update client state
$change = array();
$change["id"] = $id;
$change["star"] = $flags;
$this->updateState("star", $change);
$stat = $this->backend->SetStarFlag($this->folderid, $id, $flags, $this->contentparameters);
if (!$stat)
throw new StatusException(sprintf("ImportChangesDiff->ImportMessageStarFlag('%s','%s'): Error, unable retrieve message from backend", $id, $flags), SYNC_STATUS_OBJECTNOTFOUND);
return true;
}
/** /**
* Imports a move of a message. This occurs when a user moves an item to another folder * Imports a move of a message. This occurs when a user moves an item to another folder
* *

View file

@ -423,6 +423,117 @@ class FileStateMachine implements IStateMachine {
} }
/**
* Return if the User-Device has permission to sync against this Z-Push.
*
* @param string $user Username
* @param string $devid DeviceId
*
* @access public
* @return integer
*/
public function GetUserDevicePermission($user, $devid) {
include_once("simplemutex.php");
$mutex = new SimpleMutex();
$status = SYNC_COMMONSTATUS_SUCCESS;
$userFile = STATE_DIR . 'PreAuthUserDevices';
if ($mutex->Block()) {
if (@file_exists($userFile)) {
$userList = json_decode(@file_get_contents($userFile), true);
}
else {
$userList = Array();
}
// Android PROVISIONING initial step
if ($devid != "validate") {
$changed = false;
if (array_key_exists($user, $userList)) {
// User already pre-authorized
// User could be blocked if a "authorized" device exist and it's false
if (!$userList[$user]["authorized"]) {
$status = SYNC_COMMONSTATUS_USERDISABLEDFORSYNC;
ZLog::Write(LOGLEVEL_INFO, sprintf("FileStateMachine->GetUserDevicePermission(): Blocked user '%s', tried '%s'", $user, $devid));
}
else {
if (array_key_exists($devid, $userList[$user])) {
// Device pre-authorized found
if ($userList[$user][$devid] === false) {
$status = SYNC_COMMONSTATUS_DEVICEBLOCKEDFORUSER;
ZLog::Write(LOGLEVEL_INFO, sprintf("FileStateMachine->GetUserDevicePermission(): Blocked device '%s' for user '%s'", $devid, $user));
}
else {
ZLog::Write(LOGLEVEL_INFO, sprintf("FileStateMachine->GetUserDevicePermission(): Pre-authorized device '%s' for user '%s'", $devid, $user));
}
}
else {
// Device not pre-authorized
if (defined('PRE_AUTHORIZE_NEW_DEVICES') && PRE_AUTHORIZE_NEW_DEVICES === true) {
if (defined('PRE_AUTHORIZE_MAX_DEVICES') && PRE_AUTHORIZE_MAX_DEVICES >= count($userList[$user])) {
$userList[$user][$devid] = true;
$changed = true;
ZLog::Write(LOGLEVEL_INFO, sprintf("FileStateMachine->GetUserDevicePermission(): Pre-authorized new device '%s' for user '%s'", $devid, $user));
}
else {
$status = SYNC_COMMONSTATUS_MAXDEVICESREACHED;
ZLog::Write(LOGLEVEL_INFO, sprintf("FileStateMachine->GetUserDevicePermission(): Max number of devices reached for user '%s', tried '%s'", $user, $devid));
}
}
else {
$status = SYNC_COMMONSTATUS_DEVICEBLOCKEDFORUSER;
$userList[$user][$devid] = false;
$changed = true;
ZLog::Write(LOGLEVEL_INFO, sprintf("FileStateMachine->GetUserDevicePermission(): Blocked new device '%s' for user '%s'", $devid, $user));
}
}
}
}
else {
// User not pre-authorized
if (defined('PRE_AUTHORIZE_NEW_USERS') && PRE_AUTHORIZE_NEW_USERS === true) {
$userList[$user] = array("authorized" => true);
if (defined('PRE_AUTHORIZE_NEW_DEVICES') && PRE_AUTHORIZE_NEW_DEVICES === true) {
if (defined('PRE_AUTHORIZE_MAX_DEVICES') && PRE_AUTHORIZE_MAX_DEVICES >= count($userList[$user])) {
$userList[$user][$devid] = true;
ZLog::Write(LOGLEVEL_INFO, sprintf("FileStateMachine->GetUserDevicePermission(): Pre-authorized new device '%s' for new user '%s'", $devid, $user));
}
}
else {
$status = SYNC_COMMONSTATUS_DEVICEBLOCKEDFORUSER;
$userList[$user][$devid] = false;
ZLog::Write(LOGLEVEL_INFO, sprintf("FileStateMachine->GetUserDevicePermission(): Blocked new device '%s' for new user '%s'", $devid, $user));
}
$changed = true;
}
else {
$status = SYNC_COMMONSTATUS_USERDISABLEDFORSYNC;
$userList[$user] = array("authorized" => false, $devid => false);
$changed = true;
ZLog::Write(LOGLEVEL_INFO, sprintf("FileStateMachine->GetUserDevicePermission(): Blocked new user '%s' and device '%s'", $user, $devid));
}
}
if ($changed) {
file_put_contents($userFile, json_encode($userList));
}
}
$mutex->Release();
}
return $status;
}
/**---------------------------------------------------------------------------------------------------------- /**----------------------------------------------------------------------------------------------------------
* Private FileStateMachine stuff * Private FileStateMachine stuff
*/ */

View file

@ -0,0 +1,781 @@
<?php
/***********************************************
* File : sqlstatemachine.php
* Project : Z-Push
* Descr : This class handles state requests;
* Each Import/Export mechanism can
* store its own state information,
* which is stored through the
* state machine.
*
* Created : 25.08.2013
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class SqlStateMachine implements IStateMachine {
const SUPPORTED_STATE_VERSION = IStateMachine::STATEVERSION_02;
const VERSION = "version";
private $dbh;
private $options;
/**
* Constructor
*
* Performs some basic checks and initilizes the state directory
*
* @access public
* @throws FatalMisconfigurationException
*/
public function SqlStateMachine() {
ZLog::Write(LOGLEVEL_DEBUG, "SqlStateMachine(): init");
if (!defined('STATE_SQL_DSN') || !defined('STATE_SQL_USER') || !defined('STATE_SQL_PASSWORD')) {
throw new FatalMisconfigurationException("No configuration for the state sql database available.");
}
$this->options = array();
if (defined('STATE_SQL_OPTIONS')) {
$this->options = unserialize(STATE_SQL_OPTIONS);
}
try {
$this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options);
}
catch(PDOException $ex) {
throw new FatalMisconfigurationException(sprintf("Not possible to connect to the state database: %s", $ex->getMessage()));
}
$this->clearConnection($this->dbh);
}
/**
* Gets a hash value indicating the latest dataset of the named
* state with a specified key and counter.
* If the state is changed between two calls of this method
* the returned hash should be different
*
* @param string $devid the device id
* @param string $type the state type
* @param string $key (opt)
* @param string $counter (opt)
*
* @access public
* @return string
* @throws StateNotFoundException, StateInvalidException
*/
public function GetStateHash($devid, $type, $key = false, $counter = false) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->GetStateHash(): '%s', '%s', '%s', '%s'", $devid, $type, $key, $counter));
$sql = "select updated_at from zpush_states where device_id = :devid and state_type = :type and uuid = :key and counter = :counter";
$params = $this->getParams($devid, $type, $key, $counter);
$hash = null;
$sth = null;
$record = null;
try {
$this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options);
$sth = $this->dbh->prepare($sql);
$sth->execute($params);
$record = $sth->fetch(PDO::FETCH_ASSOC);
if (!$record) {
$this->clearConnection($this->dbh, $sth, $record);
throw new StateNotFoundException(sprintf("SqlStateMachine->GetStateHash(): Could not locate state"));
}
else {
// datetime->format("U") returns EPOCH
$datetime = new DateTime($record["updated_at"]);
$hash = $datetime->format("U");
}
}
catch(PDOException $ex) {
$this->clearConnection($this->dbh, $sth, $record);
throw new StateNotFoundException(sprintf("SqlStateMachine->GetStateHash(): Could not locate state: %s", $ex->getMessage()));
}
$this->clearConnection($this->dbh, $sth, $record);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->GetStateHash(): return '%s'", $hash));
return $hash;
}
/**
* Gets a state for a specified key and counter.
* This method sould call IStateMachine->CleanStates()
* to remove older states (same key, previous counters)
*
* @param string $devid the device id
* @param string $type the state type
* @param string $key (opt)
* @param string $counter (opt)
* @param string $cleanstates (opt)
*
* @access public
* @return mixed
* @throws StateNotFoundException, StateInvalidException
*/
public function GetState($devid, $type, $key = false, $counter = false, $cleanstates = true) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->GetState(): '%s', '%s', '%s', '%s', '%s'", $devid, $type, $key, $counter, $cleanstates));
if ($counter && $cleanstates)
$this->CleanStates($devid, $type, $key, $counter);
$sql = "select state_data from zpush_states where device_id = :devid and state_type = :type and uuid = :key and counter = :counter";
$params = $this->getParams($devid, $type, $key, $counter);
$data = null;
$sth = null;
$record = null;
try {
$this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options);
$sth = $this->dbh->prepare($sql);
$sth->execute($params);
$record = $sth->fetch(PDO::FETCH_ASSOC);
if (!$record) {
$this->clearConnection($this->dbh, $sth, $record);
// throw an exception on all other states, but not FAILSAVE as it's most of the times not there by default
if ($type !== IStateMachine::FAILSAVE) {
throw new StateNotFoundException(sprintf("SqlStateMachine->GetState(): Could not locate state"));
}
}
else {
$data = unserialize($record["state_data"]);
}
}
catch(PDOException $ex) {
$this->clearConnection($this->dbh, $sth, $record);
throw new StateNotFoundException(sprintf("SqlStateMachine->GetState(): Could not locate state: %s", $ex->getMessage()));
}
$this->clearConnection($this->dbh, $sth, $record);
return $data;
}
/**
* Writes ta state to for a key and counter
*
* @param mixed $state
* @param string $devid the device id
* @param string $type the state type
* @param string $key (opt)
* @param int $counter (opt)
*
* @access public
* @return boolean
* @throws StateInvalidException
*/
public function SetState($state, $devid, $type, $key = false, $counter = false) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->SetState(): '%s', '%s', '%s', '%s'", $devid, $type, $key, $counter));
$sql = "select device_id from zpush_states where device_id = :devid and state_type = :type and uuid = :key and counter = :counter";
$params = $this->getParams($devid, $type, $key, $counter);
$sth = null;
$record = null;
$bytes = 0;
try {
$this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options);
$sth = $this->dbh->prepare($sql);
$sth->execute($params);
$params[":data"] = serialize($state);
$params[":updated_at"] = $this->getNow();
$record = $sth->fetch(PDO::FETCH_ASSOC);
if (!$record) {
// New record
$sql = "insert into zpush_states (device_id, state_type, uuid, counter, state_data, created_at, updated_at) values (:devid, :type, :key, :counter, :data, :created_at, :updated_at)";
$params[":created_at"] = $params[":updated_at"];
$sth = $this->dbh->prepare($sql);
}
else {
// Existing record, we update it
$sql = "update zpush_states set state_data = :data, updated_at = :updated_at where device_id = :devid and state_type = :type and uuid = :key and counter = :counter";
$sth = $this->dbh->prepare($sql);
}
if (!$sth->execute($params) ) {
$this->clearConnection($this->dbh, $sth);
throw new FatalMisconfigurationException(sprintf("SqlStateMachine->SetState(): Could not write state"));
}
else {
$bytes = strlen($params[":data"]);
}
}
catch(PDOException $ex) {
$this->clearConnection($this->dbh, $sth);
throw new FatalMisconfigurationException(sprintf("SqlStateMachine->SetState(): Could not write state: %s", $ex->getMessage()));
}
$this->clearConnection($this->dbh, $sth, $record);
return $bytes;
}
/**
* Cleans up all older states
* If called with a $counter, all states previous state counter can be removed
* If called without $counter, all keys (independently from the counter) can be removed
*
* @param string $devid the device id
* @param string $type the state type
* @param string $key
* @param string $counter (opt)
*
* @access public
* @return
* @throws StateInvalidException
*/
public function CleanStates($devid, $type, $key, $counter = false) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->CleanStates(): '%s', '%s', '%s', '%s'", $devid, $type, $key, $counter));
if ($counter === false) {
// Remove all the states. Counter are -1 or > 0, then deleting >= -1 deletes all
$sql = "delete from zpush_states where device_id = :devid and state_type = :type and uuid = :key and counter >= :counter";
}
else {
$sql = "delete from zpush_states where device_id = :devid and state_type = :type and uuid = :key and counter < :counter";
}
$params = $this->getParams($devid, $type, $key, $counter);
$sth = null;
try {
$this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options);
$sth = $this->dbh->prepare($sql);
$sth->execute($params);
}
catch(PDOException $ex) {
ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->CleanStates(): Error deleting states: %s", $ex->getMessage()));
}
$this->clearConnection($this->dbh, $sth, $record);
}
/**
* Links a user to a device
*
* @param string $username
* @param string $devid
*
* @access public
* @return boolean indicating if the user was added or not (existed already)
*/
public function LinkUserDevice($username, $devid) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->LinkUserDevice(): '%s', '%s'", $username, $devid));
$sth = null;
$record = null;
$changed = false;
try {
$this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options);
$sql = "select username from zpush_users where username = :username and device_id = :devid";
$params = array(":username" => $username, ":devid" => $devid);
$sth = $this->dbh->prepare($sql);
$sth->execute($params);
$record = $sth->fetch(PDO::FETCH_ASSOC);
if ($record) {
ZLog::Write(LOGLEVEL_DEBUG, "SqlStateMachine->LinkUserDevice(): nothing changed");
}
else {
$sth = null;
$sql = "insert into zpush_users (username, device_id, created_at, updated_at) values (:username, :devid, :created_at, :updated_at)";
$params[":created_at"] = $params[":updated_at"] = $this->getNow();
$sth = $this->dbh->prepare($sql);
if ($sth->execute($params)) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->LinkUserDevice(): Linked user-device: '%s' '%s'", $username, $devid));
$changed = true;
}
else {
ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->LinkUserDevice(): Unable to link user-device"));
}
}
}
catch(PDOException $ex) {
ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->LinkUserDevice(): Error linking user-device: %s", $ex->getMessage()));
}
$this->clearConnection($this->dbh, $sth, $record);
return $changed;
}
/**
* Unlinks a device from a user
*
* @param string $username
* @param string $devid
*
* @access public
* @return boolean
*/
public function UnLinkUserDevice($username, $devid) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->UnLinkUserDevice(): '%s', '%s'", $username, $devid));
$sth = null;
$changed = false;
try {
$this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options);
$sql = "delete from zpush_users where username = :username and device_id = :devid";
$params = array(":username" => $username, ":devid" => $devid);
$sth = $this->dbh->prepare($sql);
if ($sth->execute($params)) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->UnLinkUserDevice(): Unlinked user-device: '%s' '%s'", $username, $devid));
$changed = true;
}
else {
ZLog::Write(LOGLEVEL_DEBUG, "SqlStateMachine->UnLinkUserDevice(): nothing changed");
}
}
catch(PDOException $ex) {
ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->UnLinkUserDevice(): Error unlinking user-device: %s", $ex->getMessage()));
}
$this->clearConnection($this->dbh, $sth);
return $changed;
}
/**
* Returns an array with all device ids for a user.
* If no user is set, all device ids should be returned
*
* @param string $username (opt)
*
* @access public
* @return array
*/
public function GetAllDevices($username = false) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->GetAllDevices(): '%s'", $username));
$sth = null;
$record = null;
$out = array();
try {
$this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options);
if ($username === false) {
$sql = "select distinct(device_id) from zpush_users order by device_id";
$params = array();
}
else {
$sql = "select device_id from zpush_users where username = :username order by device_id";
$params = array(":username" => $username);
}
$sth = $this->dbh->prepare($sql);
$sth->execute($params);
while ($record = $sth->fetch(PDO::FETCH_ASSOC)) {
$out[] = $record["device_id"];
}
}
catch(PDOException $ex) {
ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->GetAllDevices(): Error listing devices: %s", $ex->getMessage()));
}
$this->clearConnection($this->dbh, $sth, $record);
return $out;
}
/**
* Returns the current version of the state files
*
* @access public
* @return int
*/
public function GetStateVersion() {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->GetStateVersion()"));
$sth = null;
$record = null;
$version = IStateMachine::STATEVERSION_01;
try {
$this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options);
$sql = "select key_value from zpush_settings where key_name = :key_name";
$params = array(":key_name" => self::VERSION);
$sth = $this->dbh->prepare($sql);
$sth->execute($params);
$record = $sth->fetch(PDO::FETCH_ASSOC);
if ($record) {
$version = $record["key_value"];
}
else {
$this->SetStateVersion(self::SUPPORTED_STATE_VERSION);
$version = self::SUPPORTED_STATE_VERSION;
}
}
catch(PDOException $ex) {
ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->GetStateVersion(): Error getting state version: %s", $ex->getMessage()));
}
$this->clearConnection($this->dbh, $sth, $record);
return $version;
}
/**
* Sets the current version of the state files
*
* @param int $version the new supported version
*
* @access public
* @return boolean
*/
public function SetStateVersion($version) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->SetStateVersion(): '%s'", $version));
$sth = null;
$record = null;
$status = false;
try {
$this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options);
$sql = "select key_value from zpush_settings where key_name = :key_name";
$params = array(":key_name" => self::VERSION);
$sth = $this->dbh->prepare($sql);
$sth->execute($params);
$record = $sth->fetch(PDO::FETCH_ASSOC);
if ($record) {
$sth = null;
$sql = "update zpush_settings set key_value = :value, updated_at = :updated_at where key_name = :key_name";
$params[":value"] = $version;
$params[":updated_at"] = $this->getNow();
$sth = $this->dbh->prepare($sql);
if ($sth->execute($params)) {
$status = true;
}
}
else {
$sth = null;
$sql = "insert into zpush_settings (key_name, key_value, created_at, updated_at) values (:key_name, :value, :created_at, :updated_at)";
$params[":value"] = $version;
$params[":updated_at"] = $params[":created_at"] = $this->getNow();
$sth = $this->dbh->prepare($sql);
if ($sth->execute($params)) {
$status = true;
}
}
}
catch(PDOException $ex) {
ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->SetStateVersion(): Error saving state version: %s", $ex->getMessage()));
}
$this->clearConnection($this->dbh, $sth, $record);
return $status;
}
/**
* Returns all available states for a device id
*
* @param string $devid the device id
*
* @access public
* @return array(mixed)
*/
public function GetAllStatesForDevice($devid) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->GetAllStatesForDevice(): '%s'", $devid));
$sth = null;
$record = null;
$out = array();
try {
$this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options);
$sql = "select state_type, uuid, counter from zpush_states where device_id = :devid order by id_state";
$params = array(":devid" => $devid);
$sth = $this->dbh->prepare($sql);
$sth->execute($params);
while ($record = $sth->fetch(PDO::FETCH_ASSOC)) {
$state = array('type' => false, 'counter' => false, 'uuid' => false);
if ($record["state_type"] !== null && strlen($record["state_type"]) > 0) {
$state["type"] = $record["state_type"];
}
else {
if ($record["counter"] !== null && is_numeric($record["counter"])) {
$state["type"] = "";
}
}
if ($record["counter"] !== null && strlen($record["counter"]) > 0) {
$state["counter"] = $record["counter"];
}
if ($record["uuid"] !== null && strlen($record["uuid"]) > 0) {
$state["uuid"] = $record["uuid"];
}
$out[] = $state;
}
}
catch(PDOException $ex) {
ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->GetAllStatesForDevice(): Error listing states: %s", $ex->getMessage()));
}
$this->clearConnection($this->dbh, $sth, $record);
return $out;
}
/**
* Return if the User-Device has permission to sync against this Z-Push.
*
* @param string $user Username
* @param string $devid DeviceId
*
* @access public
* @return integer
*/
public function GetUserDevicePermission($user, $devid) {
$status = SYNC_COMMONSTATUS_SUCCESS;
$userExist = false;
$userBlocked = false;
$deviceExist = false;
$deviceBlocked = false;
// Android PROVISIONING initial step
if ($devid != "validate") {
$sth = null;
$record = null;
try {
$this->dbh = new PDO(STATE_SQL_DSN, STATE_SQL_USER, STATE_SQL_PASSWORD, $this->options);
$sql = "select authorized from zpush_preauth_users where username = :user and device_id = :devid";
$params = array(":user" => $user, ":devid" => "authorized");
$paramsNewDevid = array();
$paramsNewUser = array();
$sth = $this->dbh->prepare($sql);
$sth->execute($params);
if ($record = $sth->fetch(PDO::FETCH_ASSOC)) {
$userExist = true;
$userBlocked = !$record["authorized"];
}
$record = null;
$sth = null;
if ($userExist) {
// User already pre-authorized
// User could be blocked if a "authorized" device exist and it's false
if ($userBlocked) {
$status = SYNC_COMMONSTATUS_USERDISABLEDFORSYNC;
ZLog::Write(LOGLEVEL_INFO, sprintf("SqlStateMachine->GetUserDevicePermission(): Blocked user '%s', tried '%s'", $user, $devid));
}
else {
$params[":devid"] = $devid;
$sth = $this->dbh->prepare($sql);
$sth->execute($params);
if ($record = $sth->fetch(PDO::FETCH_ASSOC)) {
$deviceExist = true;
$deviceBlocked = !$record["authorized"];
}
$record = null;
$sth = null;
if ($deviceExist) {
// Device pre-authorized found
if ($deviceBlocked) {
$status = SYNC_COMMONSTATUS_DEVICEBLOCKEDFORUSER;
ZLog::Write(LOGLEVEL_INFO, sprintf("SqlStateMachine->GetUserDevicePermission(): Blocked device '%s' for user '%s'", $devid, $user));
}
else {
ZLog::Write(LOGLEVEL_INFO, sprintf("SqlStateMachine->GetUserDevicePermission(): Pre-authorized device '%s' for user '%s'", $devid, $user));
}
}
else {
// Device not pre-authorized
if (defined('PRE_AUTHORIZE_NEW_DEVICES') && PRE_AUTHORIZE_NEW_DEVICES === true) {
if (defined('PRE_AUTHORIZE_MAX_DEVICES') && PRE_AUTHORIZE_MAX_DEVICES >= count($userList[$user])) {
$paramsNewDevid[":auth"] = true;
ZLog::Write(LOGLEVEL_INFO, sprintf("SqlStateMachine->GetUserDevicePermission(): Pre-authorized new device '%s' for user '%s'", $devid, $user));
}
else {
$status = SYNC_COMMONSTATUS_MAXDEVICESREACHED;
ZLog::Write(LOGLEVEL_INFO, sprintf("SqlStateMachine->GetUserDevicePermission(): Max number of devices reached for user '%s', tried '%s'", $user, $devid));
}
}
else {
$status = SYNC_COMMONSTATUS_DEVICEBLOCKEDFORUSER;
$paramsNewDevid[":auth"] = false;
ZLog::Write(LOGLEVEL_INFO, sprintf("SqlStateMachine->GetUserDevicePermission(): Blocked new device '%s' for user '%s'", $devid, $user));
}
}
}
}
else {
// User not pre-authorized
if (defined('PRE_AUTHORIZE_NEW_USERS') && PRE_AUTHORIZE_NEW_USERS === true) {
$paramsNewUser[":auth"] = true;
if (defined('PRE_AUTHORIZE_NEW_DEVICES') && PRE_AUTHORIZE_NEW_DEVICES === true) {
if (defined('PRE_AUTHORIZE_MAX_DEVICES') && PRE_AUTHORIZE_MAX_DEVICES >= count($userList[$user])) {
$paramsNewDevid[":auth"] = true;
ZLog::Write(LOGLEVEL_INFO, sprintf("SqlStateMachine->GetUserDevicePermission(): Pre-authorized new device '%s' for new user '%s'", $devid, $user));
}
}
else {
$status = SYNC_COMMONSTATUS_DEVICEBLOCKEDFORUSER;
$paramsNewDevid[":auth"] = false;
ZLog::Write(LOGLEVEL_INFO, sprintf("SqlStateMachine->GetUserDevicePermission(): Blocked new device '%s' for new user '%s'", $devid, $user));
}
}
else {
$status = SYNC_COMMONSTATUS_USERDISABLEDFORSYNC;
$paramsNewUser[":auth"] = false;
$paramsNewDevid[":auth"] = false;
ZLog::Write(LOGLEVEL_INFO, sprintf("SqlStateMachine->GetUserDevicePermission(): Blocked new user '%s' and device '%s'", $user, $devid));
}
}
if (count($paramsNewUser) > 0) {
$sql = "insert into zpush_preauth_users (username, device_id, authorized, created_at, updated_at) values (:user, :devid, :auth, :created_at, :updated_at)";
$paramsNewUser[":user"] = $user;
$paramsNewUser[":devid"] = "authorized";
$paramsNewUser[":created_at"] = $paramsNewUser[":updated_at"] = $this->getNow();
$sth = $this->dbh->prepare($sql);
if (!$sth->execute($paramsNewUser)) {
ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->GetUserDevicePermission(): Error creating new user"));
$status = SYNC_COMMONSTATUS_USERDISABLEDFORSYNC;
}
}
if (count($paramsNewDevid) > 0) {
$sql = "insert into zpush_preauth_users (username, device_id, authorized, created_at, updated_at) values (:user, :devid, :auth, :created_at, :updated_at)";
$paramsNewDevid[":user"] = $user;
$paramsNewDevid[":devid"] = $devid;
$paramsNewDevid[":created_at"] = $paramsNewDevid[":updated_at"] = $this->getNow();
$sth = $this->dbh->prepare($sql);
if (!$sth->execute($paramsNewDevid)) {
ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->GetUserDevicePermission(): Error creating user new device"));
$status = SYNC_COMMONSTATUS_USERDISABLEDFORSYNC;
}
}
}
catch(PDOException $ex) {
ZLog::Write(LOGLEVEL_ERROR, sprintf("SqlStateMachine->GetUserDevicePermission(): Error checking permission for username '%s' device '%s': %s", $user, $devid, $ex->getMessage()));
$status = SYNC_COMMONSTATUS_USERDISABLEDFORSYNC;
}
$this->clearConnection($this->dbh, $sth, $record);
}
return $status;
}
/**----------------------------------------------------------------------------------------------------------
* Private SqlStateMachine stuff
*/
/**
* Return a string with the datetime NOW
*
* @return string
* @access private
*/
private function getNow() {
$now = new DateTime("NOW");
return $now->format("Y-m-d H:i:s");
}
/**
* Return an array with the params for the PDO query
*
* @params string $devid
* @params string $type
* @params string $key
* @params string $counter
* @return array
* @access private
*/
private function getParams($devid, $type, $key, $counter) {
return array(":devid" => $devid, ":type" => $type, ":key" => $key, ":counter" => ($counter === false ? -1 : $counter) );
}
/**
* Free PDO resources.
*
* @params PDOConnection $dbh
* @params PDOStatement $sth
* @params PDORecord $record
* @access private
*/
private function clearConnection(&$dbh, &$sth = null, &$record = null) {
if ($record != null) {
$record = null;
}
if ($sth != null) {
$sth = null;
}
if ($dbh != null) {
$dbh = null;
}
}
}
?>

View file

@ -289,6 +289,16 @@ interface IBackend {
* @return SyncObject $resolveRecipients * @return SyncObject $resolveRecipients
*/ */
public function ResolveRecipients($resolveRecipients); public function ResolveRecipients($resolveRecipients);
/**
* Returns the email address and the display name of the user. Used by autodiscover.
*
* @param string $username The username
*
* @access public
* @return Array
*/
public function GetUserDetails($username);
} }
?> ?>

View file

@ -98,6 +98,19 @@ interface IImportChanges extends IChanges {
*/ */
public function ImportMessageReadFlag($id, $flags); public function ImportMessageReadFlag($id, $flags);
/**
* Imports a change in 'star' flag
* This can never conflict
*
* @param string $id
* @param int $flags
*
* @access public
* @return boolean
* @throws StatusException
*/
public function ImportMessageStarFlag($id, $flags);
/** /**
* Imports a move of a message. This occurs when a user moves an item to another folder * Imports a move of a message. This occurs when a user moves an item to another folder
* *

View file

@ -188,7 +188,36 @@ class FolderSync extends RequestProcessor {
$exporter->InitializeExporter($changesMem); $exporter->InitializeExporter($changesMem);
// Stream all changes to the ImportExportChangesMem // Stream all changes to the ImportExportChangesMem
while(is_array($exporter->Synchronize())); $maxExporttime = Request::GetExpectedConnectionTimeout();
$totalChanges = $exporter->GetChangeCount();
$started = time();
$exported = 0;
$partial = false;
while(is_array($exporter->Synchronize())) {
$exported++;
if (time() % 4 ) {
self::$topCollector->AnnounceInformation(sprintf("Exported %d from %d folders", $exported, $totalChanges));
}
// if partial sync is allowed, stop if this takes too long
if (USE_PARTIAL_FOLDERSYNC && (time() - $started) > $maxExporttime) {
ZLog::Write(LOGLEVEL_WARN, sprintf("Request->HandleFolderSync(): Exporting folders is too slow. In %d seconds only %d from %d changes were processed.",(time() - $started), $exported, $totalChanges));
self::$topCollector->AnnounceInformation(sprintf("Partial export of %d out of %d folders", $exported, $totalChanges), true);
self::$deviceManager->SetFolderSyncComplete(false);
$partial = true;
break;
}
}
// update the foldersync complete flag
if (USE_PARTIAL_FOLDERSYNC && $partial == false && self::$deviceManager->GetFolderSyncComplete() === false) {
// say that we are done with partial synching
self::$deviceManager->SetFolderSyncComplete(true);
// reset the loop data to prevent any loop detection to kick in now
self::$deviceManager->ClearLoopDetectionData(Request::GetAuthUser(), Request::GetDeviceId());
ZLog::Write(LOGLEVEL_INFO, "Request->HandleFolderSync(): Chunked exporting of folders completed successfully");
}
// get the new state from the backend // get the new state from the backend
$newsyncstate = (isset($exporter))?$exporter->GetState():""; $newsyncstate = (isset($exporter))?$exporter->GetState():"";

View file

@ -90,144 +90,145 @@ class ItemOperations extends RequestProcessor {
return false; return false;
} }
if ($fetch) { // process operation
if(!self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_STORE)) while(1) {
return false; if ($fetch) {
$operation['store'] = self::$decoder->getElementContent(); if(self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_STORE)) {
if(!self::$decoder->getElementEndTag()) $operation['store'] = self::$decoder->getElementContent();
return false;//SYNC_ITEMOPERATIONS_STORE if(!self::$decoder->getElementEndTag())
return false;//SYNC_ITEMOPERATIONS_STORE
}
if(self::$decoder->getElementStartTag(SYNC_SEARCH_LONGID)) { if(self::$decoder->getElementStartTag(SYNC_SEARCH_LONGID)) {
$operation['longid'] = self::$decoder->getElementContent(); $operation['longid'] = self::$decoder->getElementContent();
if(!self::$decoder->getElementEndTag()) if(!self::$decoder->getElementEndTag())
return false;//SYNC_SEARCH_LONGID return false;//SYNC_SEARCH_LONGID
} }
if(self::$decoder->getElementStartTag(SYNC_FOLDERID)) { if(self::$decoder->getElementStartTag(SYNC_FOLDERID)) {
$operation['folderid'] = self::$decoder->getElementContent(); $operation['folderid'] = self::$decoder->getElementContent();
if(!self::$decoder->getElementEndTag()) if(!self::$decoder->getElementEndTag())
return false;//SYNC_FOLDERID return false;//SYNC_FOLDERID
} }
if(self::$decoder->getElementStartTag(SYNC_SERVERENTRYID)) { if(self::$decoder->getElementStartTag(SYNC_SERVERENTRYID)) {
$operation['serverid'] = self::$decoder->getElementContent(); $operation['serverid'] = self::$decoder->getElementContent();
if(!self::$decoder->getElementEndTag()) if(!self::$decoder->getElementEndTag())
return false;//SYNC_SERVERENTRYID return false;//SYNC_SERVERENTRYID
} }
if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_FILEREFERENCE)) { if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_FILEREFERENCE)) {
$operation['filereference'] = self::$decoder->getElementContent(); $operation['filereference'] = self::$decoder->getElementContent();
if(!self::$decoder->getElementEndTag()) if(!self::$decoder->getElementEndTag())
return false;//SYNC_AIRSYNCBASE_FILEREFERENCE return false;//SYNC_AIRSYNCBASE_FILEREFERENCE
} }
if(self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_OPTIONS)) { if(($el = self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_OPTIONS)) && ($el[EN_FLAGS] & EN_FLAGS_CONTENT)) {
//TODO other options //TODO other options
//schema //schema
//range //range
//username //username
//password //password
//bodypartpreference //bodypartpreference
//rm:RightsManagementSupport //rm:RightsManagementSupport
// Save all OPTIONS into a ContentParameters object // Save all OPTIONS into a ContentParameters object
$operation["cpo"] = new ContentParameters(); $operation["cpo"] = new ContentParameters();
while(1) { while(1) {
// Android 4.3 sends empty options tag, so we don't have to look further while (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_BODYPREFERENCE)) {
$e = self::$decoder->peek(); if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TYPE)) {
if($e[EN_TYPE] == EN_TYPE_ENDTAG) { $bptype = self::$decoder->getElementContent();
break; $operation["cpo"]->BodyPreference($bptype);
} if(!self::$decoder->getElementEndTag()) {
return false;
}
}
while (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_BODYPREFERENCE)) { if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TRUNCATIONSIZE)) {
if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TYPE)) { $operation["cpo"]->BodyPreference($bptype)->SetTruncationSize(self::$decoder->getElementContent());
$bptype = self::$decoder->getElementContent(); if(!self::$decoder->getElementEndTag())
$operation["cpo"]->BodyPreference($bptype); return false;
if(!self::$decoder->getElementEndTag()) { }
if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_ALLORNONE)) {
$operation["cpo"]->BodyPreference($bptype)->SetAllOrNone(self::$decoder->getElementContent());
if(!self::$decoder->getElementEndTag())
return false;
}
if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_PREVIEW)) {
$operation["cpo"]->BodyPreference($bptype)->SetPreview(self::$decoder->getElementContent());
if(!self::$decoder->getElementEndTag())
return false;
}
if(!self::$decoder->getElementEndTag())
return false;//SYNC_AIRSYNCBASE_BODYPREFERENCE
}
if(self::$decoder->getElementStartTag(SYNC_MIMESUPPORT)) {
$operation["cpo"]->SetMimeSupport(self::$decoder->getElementContent());
if(!self::$decoder->getElementEndTag())
return false; return false;
}
if(self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_RANGE)) {
$operation["range"] = self::$decoder->getElementContent();
if(!self::$decoder->getElementEndTag())
return false;
}
if(self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_SCHEMA)) {
// read schema tags
while (1) {
// TODO save elements
$el = self::$decoder->getElement();
$e = self::$decoder->peek();
if($e[EN_TYPE] == EN_TYPE_ENDTAG) {
self::$decoder->getElementEndTag();
break;
}
} }
} }
if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TRUNCATIONSIZE)) { //break if it reached the endtag
$operation["cpo"]->BodyPreference($bptype)->SetTruncationSize(self::$decoder->getElementContent()); $e = self::$decoder->peek();
if(!self::$decoder->getElementEndTag()) if($e[EN_TYPE] == EN_TYPE_ENDTAG) {
return false; self::$decoder->getElementEndTag();
break;
} }
if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_ALLORNONE)) {
$operation["cpo"]->BodyPreference($bptype)->SetAllOrNone(self::$decoder->getElementContent());
if(!self::$decoder->getElementEndTag())
return false;
}
if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_PREVIEW)) {
$operation["cpo"]->BodyPreference($bptype)->SetPreview(self::$decoder->getElementContent());
if(!self::$decoder->getElementEndTag())
return false;
}
if(!self::$decoder->getElementEndTag())
return false;//SYNC_AIRSYNCBASE_BODYPREFERENCE
}
if(self::$decoder->getElementStartTag(SYNC_MIMESUPPORT)) {
$operation["cpo"]->SetMimeSupport(self::$decoder->getElementContent());
if(!self::$decoder->getElementEndTag())
return false;
}
if(self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_RANGE)) {
$operation["range"] = self::$decoder->getElementContent();
if(!self::$decoder->getElementEndTag())
return false;
}
if(self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_SCHEMA)) {
// read schema tags
while (1) {
// TODO save elements
$el = self::$decoder->getElement();
$e = self::$decoder->peek();
if($e[EN_TYPE] == EN_TYPE_ENDTAG) {
self::$decoder->getElementEndTag();
break;
}
}
}
//break if it reached the endtag
$e = self::$decoder->peek();
if($e[EN_TYPE] == EN_TYPE_ENDTAG) {
self::$decoder->getElementEndTag();
break;
} }
} }
} } // end if fetch
}
if ($efc) { if ($efc) {
if(self::$decoder->getElementStartTag(SYNC_FOLDERID)) { if(self::$decoder->getElementStartTag(SYNC_FOLDERID)) {
$operation['folderid'] = self::$decoder->getElementContent(); $operation['folderid'] = self::$decoder->getElementContent();
if(!self::$decoder->getElementEndTag()) if(!self::$decoder->getElementEndTag())
return false;//SYNC_FOLDERID return false;//SYNC_FOLDERID
}
if(self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_OPTIONS)) {
if(self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_DELETESUBFOLDERS)) {
$operation['deletesubfolders'] = true;
if (($dsf = self::$decoder->getElementContent()) !== false) {
$operation['deletesubfolders'] = (boolean)$dsf;
if(!self::$decoder->getElementEndTag())
return false;
}
} }
if(self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_OPTIONS)) {
if(self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_DELETESUBFOLDERS)) {
$operation['deletesubfolders'] = true;
if (($dsf = self::$decoder->getElementContent()) !== false) {
$operation['deletesubfolders'] = (boolean)$dsf;
if(!self::$decoder->getElementEndTag())
return false;
}
}
self::$decoder->getElementEndTag();
}
}
//TODO move
//break if it reached the endtag SYNC_ITEMOPERATIONS_FETCH or SYNC_ITEMOPERATIONS_EMPTYFOLDERCONTENTS or SYNC_ITEMOPERATIONS_MOVE
$e = self::$decoder->peek();
if($e[EN_TYPE] == EN_TYPE_ENDTAG) {
self::$decoder->getElementEndTag(); self::$decoder->getElementEndTag();
break;
} }
} } // end while operation
//TODO move
if(!self::$decoder->getElementEndTag())
return false; //SYNC_ITEMOPERATIONS_FETCH or SYNC_ITEMOPERATIONS_EMPTYFOLDERCONTENTS or SYNC_ITEMOPERATIONS_MOVE
$itemoperations[] = $operation; $itemoperations[] = $operation;
//break if it reached the endtag //break if it reached the endtag
@ -236,11 +237,7 @@ class ItemOperations extends RequestProcessor {
self::$decoder->getElementEndTag(); //SYNC_ITEMOPERATIONS_ITEMOPERATIONS self::$decoder->getElementEndTag(); //SYNC_ITEMOPERATIONS_ITEMOPERATIONS
break; break;
} }
} // end operations loop
}
// if(!self::$decoder->getElementEndTag())
// return false;//SYNC_ITEMOPERATIONS_ITEMOPERATIONS
$status = SYNC_ITEMOPERATIONSSTATUS_SUCCESS; $status = SYNC_ITEMOPERATIONSSTATUS_SUCCESS;

View file

@ -113,8 +113,12 @@ class Request {
self::$command = self::filterEvilInput($_GET["Cmd"], self::LETTERS_ONLY); self::$command = self::filterEvilInput($_GET["Cmd"], self::LETTERS_ONLY);
// getUser is unfiltered, as everything is allowed.. even "/", "\" or ".." // getUser is unfiltered, as everything is allowed.. even "/", "\" or ".."
if(isset($_GET["User"])) if(isset($_GET["User"])) {
self::$getUser = strtolower($_GET["User"]); self::$getUser = strtolower($_GET["User"]);
if(defined('USE_FULLEMAIL_FOR_LOGIN') && ! USE_FULLEMAIL_FOR_LOGIN) {
self::$getUser = Utils::GetLocalPartFromEmail(self::$getUser);
}
}
if(isset($_GET["DeviceId"])) if(isset($_GET["DeviceId"]))
self::$devid = strtolower(self::filterEvilInput($_GET["DeviceId"], self::WORDCHAR_ONLY)); self::$devid = strtolower(self::filterEvilInput($_GET["DeviceId"], self::WORDCHAR_ONLY));
if(isset($_GET["DeviceType"])) if(isset($_GET["DeviceType"]))
@ -140,8 +144,12 @@ class Request {
if (!isset(self::$command) && isset($query['Command'])) if (!isset(self::$command) && isset($query['Command']))
self::$command = Utils::GetCommandFromCode($query['Command']); self::$command = Utils::GetCommandFromCode($query['Command']);
if (!isset(self::$getUser) && isset($query[self::COMMANDPARAM_USER])) if (!isset(self::$getUser) && isset($query[self::COMMANDPARAM_USER])) {
self::$getUser = strtolower($query[self::COMMANDPARAM_USER]); self::$getUser = strtolower($query[self::COMMANDPARAM_USER]);
if(defined('USE_FULLEMAIL_FOR_LOGIN') && ! USE_FULLEMAIL_FOR_LOGIN) {
self::$getUser = Utils::GetLocalPartFromEmail(self::$getUser);
}
}
if (!isset(self::$devid) && isset($query['DevID'])) if (!isset(self::$devid) && isset($query['DevID']))
self::$devid = strtolower(self::filterEvilInput($query['DevID'], self::WORDCHAR_ONLY)); self::$devid = strtolower(self::filterEvilInput($query['DevID'], self::WORDCHAR_ONLY));
@ -172,8 +180,12 @@ class Request {
} }
// in base64 encoded query string user is not necessarily set // in base64 encoded query string user is not necessarily set
if (!isset(self::$getUser) && isset($_SERVER['PHP_AUTH_USER'])) if (!isset(self::$getUser) && isset($_SERVER['PHP_AUTH_USER'])) {
list(self::$getUser,) = Utils::SplitDomainUser(strtolower($_SERVER['PHP_AUTH_USER'])); list(self::$getUser,) = Utils::SplitDomainUser(strtolower($_SERVER['PHP_AUTH_USER']));
if(defined('USE_FULLEMAIL_FOR_LOGIN') && ! USE_FULLEMAIL_FOR_LOGIN) {
self::$getUser = Utils::GetLocalPartFromEmail(self::$getUser);
}
}
} }
/** /**
@ -233,6 +245,9 @@ class Request {
list(self::$authUser, self::$authDomain) = Utils::SplitDomainUser($_SERVER['PHP_AUTH_USER']); list(self::$authUser, self::$authDomain) = Utils::SplitDomainUser($_SERVER['PHP_AUTH_USER']);
self::$authPassword = (isset($_SERVER['PHP_AUTH_PW']))?$_SERVER['PHP_AUTH_PW'] : ""; self::$authPassword = (isset($_SERVER['PHP_AUTH_PW']))?$_SERVER['PHP_AUTH_PW'] : "";
} }
if(defined('USE_FULLEMAIL_FOR_LOGIN') && ! USE_FULLEMAIL_FOR_LOGIN) {
self::$authUser = Utils::GetLocalPartFromEmail(self::$authUser);
}
// authUser & authPassword are unfiltered! // authUser & authPassword are unfiltered!
return (self::$authUser != "" && self::$authPassword != ""); return (self::$authUser != "" && self::$authPassword != "");
} }
@ -566,6 +581,32 @@ class Request {
return (isset(self::$headers["content-length"]))? (int) self::$headers["content-length"] : 0; return (isset(self::$headers["content-length"]))? (int) self::$headers["content-length"] : 0;
} }
/**
* Returns the amount of seconds this request is able to be kept open without the client
* closing it. This depends on the vendor.
*
* @access public
* @return boolean
*/
static public function GetExpectedConnectionTimeout() {
// Different vendors implement different connection timeouts.
// In order to optimize processing, we return a specific time for the major
// classes currently known (feedback welcome).
// The amount of time returned is somehow lower than the max timeout so we have
// time for processing.
// Apple and Windows Phone have higher timeouts (4min = 240sec)
if (in_array(self::GetDeviceType(), array("iPod", "iPad", "iPhone", "WP"))) {
return 200;
}
// Samsung devices have a intermediate timeout (90sec)
if (in_array(self::GetDeviceType(), array("SAMSUNGGTI"))) {
return 50;
}
// for all other devices, a timeout of 30 seconds is expected
return 20;
}
/**---------------------------------------------------------------------------------------------------------- /**----------------------------------------------------------------------------------------------------------
* Private stuff * Private stuff

View file

@ -401,25 +401,27 @@ class Search extends RequestProcessor {
} }
} }
elseif ($searchname == ISearchProvider::SEARCH_MAILBOX) { elseif ($searchname == ISearchProvider::SEARCH_MAILBOX) {
foreach ($rows as $u) { if (is_array($rows) && !empty($rows)) {
self::$encoder->startTag(SYNC_SEARCH_RESULT); foreach ($rows as $u) {
self::$encoder->startTag(SYNC_FOLDERTYPE); self::$encoder->startTag(SYNC_SEARCH_RESULT);
self::$encoder->content($u['class']); self::$encoder->startTag(SYNC_FOLDERTYPE);
self::$encoder->endTag(); self::$encoder->content($u['class']);
self::$encoder->startTag(SYNC_SEARCH_LONGID); self::$encoder->endTag();
self::$encoder->content($u['longid']); self::$encoder->startTag(SYNC_SEARCH_LONGID);
self::$encoder->endTag(); self::$encoder->content($u['longid']);
self::$encoder->startTag(SYNC_FOLDERID); self::$encoder->endTag();
self::$encoder->content($u['folderid']); self::$encoder->startTag(SYNC_FOLDERID);
self::$encoder->endTag(); self::$encoder->content($u['folderid']);
self::$encoder->endTag();
self::$encoder->startTag(SYNC_SEARCH_PROPERTIES); self::$encoder->startTag(SYNC_SEARCH_PROPERTIES);
$tmp = explode(":", $u['longid']); $tmp = explode(":", $u['longid']);
$message = self::$backend->Fetch($u['folderid'], $tmp[1], $cpo); $message = self::$backend->Fetch($u['folderid'], $tmp[1], $cpo);
$message->Encode(self::$encoder); $message->Encode(self::$encoder);
self::$encoder->endTag();//result self::$encoder->endTag();//result
self::$encoder->endTag();//properties self::$encoder->endTag();//properties
}
} }
} }
// it seems that android 4 requires range and searchtotal // it seems that android 4 requires range and searchtotal

View file

@ -62,6 +62,18 @@ class Sync extends RequestProcessor {
$wbxmlproblem = false; $wbxmlproblem = false;
$emptysync = false; $emptysync = false;
// check if the hierarchySync was fully completed
if (USE_PARTIAL_FOLDERSYNC) {
if (self::$deviceManager->GetFolderSyncComplete() === false) {
ZLog::Write(LOGLEVEL_INFO, "Request->HandleSync(): Sync request aborted, as exporting of folders has not yet completed");
self::$topCollector->AnnounceInformation("Aborted due incomplete folder sync", true);
$status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
}
else
ZLog::Write(LOGLEVEL_INFO, "Request->HandleSync(): FolderSync marked as complete");
}
// Start Synchronize // Start Synchronize
if(self::$decoder->getElementStartTag(SYNC_SYNCHRONIZE)) { if(self::$decoder->getElementStartTag(SYNC_SYNCHRONIZE)) {
@ -189,7 +201,7 @@ class Sync extends RequestProcessor {
else else
$supfields[] = $el[EN_TAG]; $supfields[] = $el[EN_TAG];
} }
self::$deviceManager->SetSupportedFields($spa->GetFolderId(), $supfields); self::$deviceManager->SetSupportedFields($spa->GetFolderId(), $supfields);
} }
} }

View file

@ -125,7 +125,7 @@ class SyncAppointment extends SyncObject {
// 3 = Out of office // 3 = Out of office
SYNC_POOMCAL_BUSYSTATUS => array ( self::STREAMER_VAR => "busystatus", SYNC_POOMCAL_BUSYSTATUS => array ( self::STREAMER_VAR => "busystatus",
self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETTWO, self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETTWO,
self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2,3) )), self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2,3,4) )),
SYNC_POOMCAL_ALLDAYEVENT => array ( self::STREAMER_VAR => "alldayevent", SYNC_POOMCAL_ALLDAYEVENT => array ( self::STREAMER_VAR => "alldayevent",
self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ZEROORONE => self::STREAMER_CHECK_SETZERO)), self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ZEROORONE => self::STREAMER_CHECK_SETZERO)),

View file

@ -164,7 +164,7 @@ class SyncMail extends SyncObject {
self::STREAMER_ARRAY => SYNC_AIRSYNCBASE_ATTACHMENT); self::STREAMER_ARRAY => SYNC_AIRSYNCBASE_ATTACHMENT);
$mapping[SYNC_POOMMAIL_CONTENTCLASS] = array ( self::STREAMER_VAR => "contentclass", $mapping[SYNC_POOMMAIL_CONTENTCLASS] = array ( self::STREAMER_VAR => "contentclass",
self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(DEFAULT_EMAIL_CONTENTCLASS) )); self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(DEFAULT_EMAIL_CONTENTCLASS, DEFAULT_CALENDAR_CONTENTCLASS) ));
$mapping[SYNC_POOMMAIL_FLAG] = array ( self::STREAMER_VAR => "flag", $mapping[SYNC_POOMMAIL_FLAG] = array ( self::STREAMER_VAR => "flag",
self::STREAMER_TYPE => "SyncMailFlags", self::STREAMER_TYPE => "SyncMailFlags",

View file

@ -62,6 +62,7 @@ class SyncMeetingRequest extends SyncObject {
public $busystatus; public $busystatus;
public $timezone; public $timezone;
public $globalobjid; public $globalobjid;
public $disallownewtimeproposal;
function SyncMeetingRequest() { function SyncMeetingRequest() {
$mapping = array ( $mapping = array (
@ -126,6 +127,10 @@ class SyncMeetingRequest extends SyncObject {
self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => base64_encode(pack("la64vvvvvvvv"."la64vvvvvvvv"."l",0,"",0,0,0,0,0,0,0,0,0,"",0,0,0,0,0,0,0,0,0)) )), self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => base64_encode(pack("la64vvvvvvvv"."la64vvvvvvvv"."l",0,"",0,0,0,0,0,0,0,0,0,"",0,0,0,0,0,0,0,0,0)) )),
SYNC_POOMMAIL_GLOBALOBJID => array ( self::STREAMER_VAR => "globalobjid"), SYNC_POOMMAIL_GLOBALOBJID => array ( self::STREAMER_VAR => "globalobjid"),
SYNC_POOMMAIL_DISALLOWNEWTIMEPROPOSAL => array ( self::STREAMER_VAR => "disallownewtimeproposal",
self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETZERO,
self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
); );
parent::SyncObject($mapping); parent::SyncObject($mapping);

View file

@ -49,10 +49,54 @@ if (!function_exists("quoted_printable_encode")) {
* *
* @param string $string string to be encoded * @param string $string string to be encoded
* *
* @see http://www.php.net/manual/en/function.quoted-printable-decode.php#89417 * @see http://www.php.net/manual/en/function.quoted-printable-encode.php#106078
*/ */
function quoted_printable_encode($string) { function quoted_printable_encode($string) {
return preg_replace('/[^\r\n]{73}[^=\r\n]{2}/', "$0=\n", str_replace(array('%20', '%0D%0A', '%'), array(' ', "\r\n", '='), rawurlencode($string))); $lp = 0;
$ret = '';
$hex = "0123456789ABCDEF";
$length = strlen($str);
$str_index = 0;
while ($length--) {
if ((($c = $str[$str_index++]) == "\015") && ($str[$str_index] == "\012") && $length > 0) {
$ret .= "\015";
$ret .= $str[$str_index++];
$length--;
$lp = 0;
} else {
if (ctype_cntrl($c)
|| (ord($c) == 0x7f)
|| (ord($c) & 0x80)
|| ($c == '=')
|| (($c == ' ') && ($str[$str_index] == "\015")))
{
if (($lp += 3) > PHP_QPRINT_MAXL)
{
$ret .= '=';
$ret .= "\015";
$ret .= "\012";
$lp = 3;
}
$ret .= '=';
$ret .= $hex[ord($c) >> 4];
$ret .= $hex[ord($c) & 0xf];
}
else
{
if ((++$lp) > PHP_QPRINT_MAXL)
{
$ret .= '=';
$ret .= "\015";
$ret .= "\012";
$lp = 1;
}
$ret .= $c;
}
}
}
return $ret;
} }
} }

View file

@ -1100,11 +1100,23 @@ class TimezoneUtil {
ZLog::Write(LOGLEVEL_DEBUG, "TimezoneUtil::GetFullTZ() for ". $phptimezone); ZLog::Write(LOGLEVEL_DEBUG, "TimezoneUtil::GetFullTZ() for ". $phptimezone);
$servertzname = self::guessTZNameFromPHPName($phptimezone); $servertzname = self::guessTZNameFromPHPName($phptimezone);
$offset = self::$tzonesoffsets[$servertzname]; return self::GetFullTZFromTZName($servertzname);
}
/**
* Returns a full timezone array
*
* @param string $tzname a TZID value
*
* @access public
* @return array
*/
static public function GetFullTZFromTZName($tzname) {
$offset = self::$tzonesoffsets[$tzname];
$tz = array( $tz = array(
"bias" => $offset[0], "bias" => $offset[0],
"tzname" => self::encodeTZName(self::getMSTZnameFromTZName($servertzname)), "tzname" => self::encodeTZName(self::getMSTZnameFromTZName($tzname)),
"dstendyear" => $offset[3], "dstendyear" => $offset[3],
"dstendmonth" => $offset[4], "dstendmonth" => $offset[4],
"dstendday" => $offset[6], "dstendday" => $offset[6],
@ -1114,7 +1126,7 @@ class TimezoneUtil {
"dstendsecond" => $offset[9], "dstendsecond" => $offset[9],
"dstendmillis" => $offset[10], "dstendmillis" => $offset[10],
"stdbias" => $offset[1], "stdbias" => $offset[1],
"tznamedst" => self::encodeTZName(self::getMSTZnameFromTZName($servertzname)), "tznamedst" => self::encodeTZName(self::getMSTZnameFromTZName($tzname)),
"dststartyear" => $offset[11], "dststartyear" => $offset[11],
"dststartmonth" => $offset[12], "dststartmonth" => $offset[12],
"dststartday" => $offset[14], "dststartday" => $offset[14],
@ -1258,6 +1270,26 @@ class TimezoneUtil {
} }
} }
/**
* Pack timezone info for Sync
*
* @param array $tz
*
* @access private
* @return string
*/
static public function GetSyncBlobFromTZ($tz) {
// set the correct TZ name (done using the Bias)
if (!isset($tz["tzname"]) || !$tz["tzname"] || !isset($tz["tznamedst"]) || !$tz["tznamedst"])
$tz = TimezoneUtil::FillTZNames($tz);
$packed = pack("la64vvvvvvvv" . "la64vvvvvvvv" . "l",
$tz["bias"], $tz["tzname"], 0, $tz["dstendmonth"], $tz["dstendday"], $tz["dstendweek"], $tz["dstendhour"], $tz["dstendminute"], $tz["dstendsecond"], $tz["dstendmillis"],
$tz["stdbias"], $tz["tznamedst"], 0, $tz["dststartmonth"], $tz["dststartday"], $tz["dststartweek"], $tz["dststarthour"], $tz["dststartminute"], $tz["dststartsecond"], $tz["dststartmillis"],
$tz["dstbias"]);
return $packed;
}
} }
?> ?>

View file

@ -917,6 +917,78 @@ class Utils {
return 0; return 0;
} }
/**
* Returns the local part from email address.
*
* @param string $email
*
* @access public
* @return string
*/
public static function GetLocalPartFromEmail($email) {
$pos = strpos($email, '@');
if ($pos === false) {
return $email;
}
return substr($email, 0, $pos);
}
/**
* Generate date object from string and timezone.
*
* @param string $value
* @param string $timezone
*
* @access public
* @return int epoch
*/
public static function MakeUTCDate($value, $timezone = null) {
$tz = null;
if ($timezone) {
$tz = timezone_open($timezone);
}
if (!$tz) {
//If there is no timezone set, we use the default timezone
$tz = timezone_open(date_default_timezone_get());
}
//20110930T090000Z
$date = date_create_from_format('Ymd\THis\Z', $value, timezone_open("UTC"));
if (!$date) {
//20110930T090000
$date = date_create_from_format('Ymd\THis', $value, $tz);
}
if (!$date) {
//20110930 (Append T000000Z to the date, so it starts at midnight)
$date = date_create_from_format('Ymd\THis\Z', $value . "T000000Z", $tz);
}
return date_timestamp_get($date);
}
/**
* Generate a tzid from various formats
*
* @param str $timezone
*
* @access public
* @return timezone id
*/
public static function ParseTimezone($timezone) {
//(GMT+01.00) Amsterdam / Berlin / Bern / Rome / Stockholm / Vienna
if (preg_match('/GMT(\\+|\\-)0(\d)/', $timezone, $matches)) {
return "Etc/GMT" . $matches[1] . $matches[2];
}
//(GMT+10.00) XXX / XXX / XXX / XXX
if (preg_match('/GMT(\\+|\\-)1(\d)/', $timezone, $matches)) {
return "Etc/GMT" . $matches[1] . "1" . $matches[2];
}
///inverse.ca/20101018_1/Europe/Amsterdam or /inverse.ca/20101018_1/America/Argentina/Buenos_Aires
if (preg_match('/\/[.[:word:]]+\/\w+\/(\w+)\/([\w\/]+)/', $timezone, $matches)) {
return $matches[1] . "/" . $matches[2];
}
return trim($timezone, '"');
}
} }

19
sources/sql/mysql.sql Normal file
View file

@ -0,0 +1,19 @@
create table zpush_settings (key_name varchar(50) not null, key_value varchar(50) not null, created_at datetime not null, updated_at datetime not null, primary key (key_name));
create table zpush_users (username varchar(50) not null, device_id varchar(50) not null, created_at datetime not null, updated_at datetime not null, primary key (username, device_id));
create table zpush_states (id_state integer auto_increment, device_id varchar(50) not null, uuid varchar(50), state_type varchar(50), counter integer, state_data mediumtext not null,
created_at datetime not null, updated_at datetime not null, primary key (id_state));
create unique index idx_zpush_states_unique on zpush_states (device_id, uuid, state_type, counter);
-- This is optional, and will require extra configuration in your mysql
-- http://www.mysqlperformanceblog.com/2012/05/30/data-compression-in-innodb-for-text-and-blob-fields/
alter table zpush_states engine=InnoDB row_format=compressed key_block_size=16;
-- This table has a primary key id integer, because I will be linking a Rails model against it (admin wui)
create table zpush_preauth_users (id integer auto_increment, username varchar(50) not null, device_id varchar(50) not null, authorized boolean not null,
created_at datetime not null, updated_at datetime not null, primary key (id));
create unique index index_zpush_preauth_users_on_username_and_device_id on zpush_preauth_users (username, device_id);

View file

@ -0,0 +1,51 @@
BEGIN:VCALENDAR
METHOD:REQUEST
PRODID:Microsoft Exchange Server 2010
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Romance Standard Time
BEGIN:STANDARD
DTSTART:16010101T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010101T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
ORGANIZER;CN=Pablo Marmol:MAILTO:pablo.marmol@zpush.org
ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=pedro.pica
piedra@zpush.org:MAILTO:pedro.picapiedra@zpush.org
DESCRIPTION;LANGUAGE=es-ES:Texto de la segunda cita\n\n
SUMMARY;LANGUAGE=es-ES:Segunda cita
DTSTART;TZID=Romance Standard Time:20140519T090000
DTEND;TZID=Romance Standard Time:20140519T093000
UID:040000008200E00074C5B7101A82E0080000000070BC3EB80871CF01000000000000000
010000000B3492E4691795F4E810CCD60A178B53C
CLASS:PUBLIC
PRIORITY:5
DTSTAMP:20140516T111408Z
TRANSP:OPAQUE
STATUS:CONFIRMED
SEQUENCE:0
LOCATION;LANGUAGE=es-ES:Oficina
X-MICROSOFT-CDO-APPT-SEQUENCE:0
X-MICROSOFT-CDO-OWNERAPPTID:860903390
X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE
X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
X-MICROSOFT-CDO-ALLDAYEVENT:FALSE
X-MICROSOFT-CDO-IMPORTANCE:1
X-MICROSOFT-CDO-INSTTYPE:0
X-MICROSOFT-DISALLOW-COUNTER:FALSE
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:REMINDER
TRIGGER;RELATED=START:-PT15M
END:VALARM
END:VEVENT
END:VCALENDAR

View file

@ -0,0 +1,28 @@
Return-Path: <xxxx.xxxx@xxxx.xxx>
X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on xxxxx
X-Spam-Level:
X-Spam-Status: No, score=-1.0 required=5.0 tests=ALL_TRUSTED autolearn=ham
autolearn_force=no version=3.4.0
Delivered-To: xxxx@xxxxxxxxxxxx
Received: from localhost (xxxx.xxxxxxxxx.xx [127.0.0.1])
by xxxx.xxxxxxxxxxx.xx (Postfix) with ESMTP id D8AB65001D6
for <xxxxx@xxxxxxxx.xx>; Sun, 20 Jul 2014 15:30:58 +0200 (CEST)
X-Virus-Scanned: Debian amavisd-new at xxxx.xxxxxxxxx.xx
Received: from xxxx.xxxxxxxxx.xxx ([127.0.0.1])
by localhost (xxxx.xxxxxxxxxx.xx [127.0.0.1]) (amavisd-new, port 10024)
with ESMTP id vI3uJWoUh-Pz for <xxxx@xxxxxxxxx.xx>;
Sun, 20 Jul 2014 15:30:58 +0200 (CEST)
Received-SPF: none (xxxx.xxx: No applicable sender policy available) receiver=xxxx.xxxxxxxxxx.xx; identity=mailfrom; envelope-from="xxxx.xxxxx@xxxx.xxxx"; helo=$
From: xxxxxxxxxx <xxxx.xxxx@xxxx.xxx>
Content-Type: text/plain;
charset=utf-8
Content-Transfer-Encoding: quoted-printable
Mime-Version: 1.0 (1.0)
Subject: Testing
Message-Id: <BB54ADE1-199F-4920-863C-357EE09AE735@xxxx.xxx>
Date: Sun, 20 Jul 2014 15:31:09 +0200
To: xxxx xxxxx<xxxx@xxxxxxxx.xx>
Testing emojis =F0=9F=98=84
Sendt fra min iPad=

View file

@ -0,0 +1,39 @@
Return-Path: xxxxxx@xxxxxxxx.xxx
X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on mail.xxxxxxxxx.xx
X-Spam-Level:
X-Spam-Status: No, score=-1.0 required=5.0 tests=ALL_TRUSTED, HEADER_FROM_DIFFERENT_DOMAINS autolearn=unavailable autolearn_force=no version=3.4.0
Delivered-To: xxxxxxx@xxxxxxxxxxx.xx
Received: from localhost (xxxxx.xxxxxx.xx [127.0.0.1]) by xxxxx.xxxxxxxxxx.xx (Postfix) with ESMTP id 1982C500AD4; Fri, 11 Jul 2014 21:20:53 +0200 (CEST)
X-Virus-Scanned: Debian amavisd-new at xxxxx.xxxxxxx.xx
Received: from xxxxx.xxxxxxxxxx.xx ([127.0.0.1]) by localhost (xxxx.xxxxxxxx.xx [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 2nP27OZ1CUzN; Fri, 11 Jul 2014 21:20:52 +0200 (CEST)
Resent-From: xxxxx@xxxxxxx.xx
DMARC-Filter: OpenDMARC Filter v1.2.0 xxxx.xxxxxx.xxx 9Dxxx7500A9D
X-Virus-Scanned: Debian amavisd-new at xxxxx.xxxxxxxxxx.xx
MIME-Version: 1.0
DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=xxxxxxxx.xx; s=mail; t=1405106446; bh=ufc8aM3vMmQISb8aKXpA3Rk8t/+ce6kFhZUr1x6DwtU=; h=From:Subject:Date:To:From; b=uQb7eWfRZdkUF+didgfmgJTWW78GdyHJlEqhz+WFl2Ouqkt7iXZPwljR4OIoFvumK1IJqsXWRW74Py+Mq2CWkxeuML/9kTrWBXJDMichOqll666EgG8/DyyYH6RPIhje9ygILX85E4VO0zO1N4uh0Tc/D0Ow93hUWS51D1cX7gA=
From: xxxxx@xxxxxxxx.xx
Subject: Kontroll
Message-ID: 28EF0B82-61D9-4BA3-B534-CE4FB6101D1A@xxxxxx.xxx
Date: Fri, 11 Jul 2014 21:21:03 +0200
To: stxxx xxxxx@xxxxxxxxx.xx
Content-Type: multipart/mixed; boundary="=_83c3cde24966264fd2b3537156aaaba6"
Received-SPF: Pass (xxxx.xxxxxxxx.loc: domain of xxxxxx@xxxxxx.xx designates xxx.xxx.x.xx as permitted sender) receiver=xxxx.xxxxxxxx.xxx; client-ip=xxx.xxx.x.xx; helo=xxxxx.xxxxxxx.xx;
X-Auto-Response-Suppress: DR, OOF, AutoReply
Resent-Message-Id: 20140711192053.1982C500AD4@xxxxxx.xxxxxx.xx
Resent-Date: Fri, 11 Jul 2014 21:20:53 +0200 (CEST)
--=_83c3cde24966264fd2b3537156aaaba6
Content-Type: multipart/alternative; boundary="=_3eaa2d9769aa280737bc3e5268efb2ad"
--=_3eaa2d9769aa280737bc3e5268efb2ad
Content-Transfer-Encoding: base64
Content-Type: text/plain; charset="utf-8"
RGV0IGVyIHDDpSB0aWRlIGF0dCB2aSBwbGFubGVnZ2VyIHVuaWZvcm1zIGtvbnRyb2xsIA0KS3Zh
ciBoZWxnIG5vZW4gc29tIGlra2plIGhhcg0KQWxsZSBtw6Uga3VubmUgZ2rDuHIgZW4gaW5uc2F0
cyBldHRlciBmZXJpZW4gMiAgcGVycyB4IDQgaGVsZ2VyIHPDpSB0cm9yIGVnIG15ZSBhdiBwcm9i
bGVtbWV0IGVyIHZla2tlDQpHb2Qgc29tbWVyIG9nIGZlcmllIPCfmI4NClZpYmVrZQ0KDQpTZW5k
dCBmcmEgbWluIGlQaG9uZQ==
--=_3eaa2d9769aa280737bc3e5268efb2ad--
--=_83c3cde24966264fd2b3537156aaaba6--

View file

@ -0,0 +1,20 @@
Mime-Version: 1.0
From: xxxx@xxxxxxx.xxx
Subject: =?utf-8?B?8J+YhA==?=
Message-id: EC08EA2B-1439-423D-AA69-843776D5C39C@xxxxxxxx.xx
Date: Wed, 23 Jul 2014 19:50:36 +0200
To: test2014@xxxxxxxxxxx.xx
Content-Type: multipart/mixed; boundary="=_72ced2dca575896ff9586958e1bb6592"
This is a multi-part message in MIME format.
--=_72ced2dca575896ff9586958e1bb6592
Content-Type: multipart/alternative; boundary="=_9818f474f7be6252ec285590ed281857"
--=_9818f474f7be6252ec285590ed281857
Content-Transfer-Encoding: base64
Content-Type: text/plain; charset=utf-8
8J+Y4oCeDQoNClNlbmR0IGZyYSBtaW4gaVBob25l
--=_9818f474f7be6252ec285590ed281857--
--=_72ced2dca575896ff9586958e1bb6592--

View file

@ -0,0 +1,48 @@
Return-Path: <edt@domain.com>
Delivered-To: informatique@domain.com
Received: from localhost (localhost.localdomain [127.0.0.1])
by domain.com (Postfix) with ESMTP id AB32E1000EA
for <informatique@domain.com>; Sun, 7 Sep 2014 09:50:21 +0200 (CEST)
Authentication-Results: domain.com (amavisd-new);
dkim=pass (1024-bit key) reason="pass (just generated, assumed good)"
header.d=domain.com
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=
domain.com; h=content-transfer-encoding:content-type
:content-type:mime-version:from:from:subject:subject:date:date
:received; s=mail; t=1410076220; x=1411890621; bh=paEf4SYhXmrsFW
vtqTtrw7K9W9eprm8TMRs5JiO2p6o=; b=Dszu0yEi/ktiqQoFdLPKxkvdDB+sU+
3b+B2O0Jf2PzVahHvMj7bGJSoW0eVb1u6tot6q5NOjFH85Ab1MZdhoga+jMW3h8E
5GrVvA4NumMV/HMJ8M/1LpAc5nTaGxN+8KfGHxCM0DsjZEFwuLwL3xU6PgSrggcC
6DT6GcpnPTRxM=
X-Virus-Scanned: Debian amavisd-new at domain.com
X-Spam-Flag: NO
X-Spam-Score: 0.138
X-Spam-Level:
X-Spam-Status: No, score=0.138 tagged_above=-999 required=4
tests=[MISSING_MID=0.14, NO_RECEIVED=-0.001, NO_RELAYS=-0.001]
autolearn=no
Received: from domain.com ([127.0.0.1])
by localhost (domain.com [127.0.0.1]) (amavisd-new, port 10024)
with ESMTP id YDofG_pOTs47 for <informatique@domain.com>;
Sun, 7 Sep 2014 09:50:20 +0200 (CEST)
Date: Sun, 07 Sep 2014 09:50:03 +0200
Subject: =?ISO-8859-1?Q?Re:_[TC5_BAT]_Construction_g=E9n=E9rale_/_08:30?=
From: =?ISO-8859-1?Q?Edt_Secr=E9tariat_ESITC_Cachan?=
<edt@domain.com>
To: Informatique ESITC <informatique@domain.com>
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: base64
Message-Id: <20140907075021.AB32E1000EA@domain.com>
Qydlc3QgcmVjdGlmacOpLgpCb25uZSBqb3VybsOpZS4KTGUgc2VjcsOpdGFyaWF0IGVkdMKgIGRl
IGwnRVNJVEMKCkxlIDYgc2VwdC4gMjAxNCAyMzo0NCwgSW5mb3JtYXRpcXVlIEVTSVRDIDxpbmZv
cm1hdGlxdWVAYWRtLmVzaXRjLWNhY2hhbi5mcj4gYSDDqWNyaXQgOgo+Cj4gQm9uam91ciwgCj4K
PiBJbCBzZW1ibGVyYWl0IHF1J2lsIHkgYWl0IHVuZSBwZXRpdGUgZXJyZXVyIHN1ciBsJ2VtcGxv
aSBkdSB0ZW1wcyBkZXMgCj4gVEM1IEJBVC4gCj4KPiBMZSBMdW5kaSAwNSBKYW52aWVyIDIwMTUg
w6AgMDhIMzAuIExlIGNvdXJzIGRlIENHIGR1cmUgMWgzMCBhbG9ycyBxdSdpbCB5IAo+IGEgMiBz
w6lhbmNlcy4gCj4gRG9uYyBzb2l0IGMnZXN0IAo+IFNvaXQgdW5lIHPDqWFuY2UgZGUgMUgzMCAK
PiBTb2l0IDIgc8OpYW5jZXMgZGUgM0ggKGF1IHRvdGFsKSAKPiBNYWlzIHBhcyAyIHPDqWFuY2Vz
IGRlIDFIMzAgKGF1IHRvdGFsKSAKPgo+IENvcmRpYWxlbWVudCwgCj4gSW5mb3JtYXRpcXVlIEVT
SVRDIAo=

View file

@ -0,0 +1,31 @@
From: "Doug Sauder" <doug@example.com>
To: "Jürgen Schmürgen" <schmuergen@example.com>
Subject: Die Hasen und die Frösche (Microsoft Outlook 00)
Date: Wed, 17 May 2000 19:08:29 -0400
Message-ID: <NDBBIAKOPKHFGPLCODIGIEKBCHAA.doug@example.com>
MIME-Version: 1.0
Content-Type: text/plain;
charset="iso-8859-1"
Content-Transfer-Encoding: 8bit
X-Priority: 3 (Normal)
X-MSMail-Priority: Normal
X-Mailer: Microsoft Outlook IMO, Build 9.0.2416 (9.0.2910.0)
Importance: Normal
X-MimeOLE: Produced By Microsoft MimeOLE V5.00.2314.1300
Die Hasen und die Frösche
Die Hasen klagten einst über ihre mißliche Lage; "wir leben", sprach ein
Redner, "in steter Furcht vor Menschen und Tieren, eine Beute der Hunde, der
Adler, ja fast aller Raubtiere! Unsere stete Angst ist ärger als der Tod
selbst. Auf, laßt uns ein für allemal sterben."
In einem nahen Teich wollten sie sich nun ersäufen; sie eilten ihm zu;
allein das außerordentliche Getöse und ihre wunderbare Gestalt erschreckte
eine Menge Frösche, die am Ufer saßen, so sehr, daß sie aufs schnellste
untertauchten.
"Halt", rief nun eben dieser Sprecher, "wir wollen das Ersäufen noch ein
wenig aufschieben, denn auch uns fürchten, wie ihr seht, einige Tiere,
welche also wohl noch unglücklicher sein müssen als wir."

View file

@ -0,0 +1,31 @@
From: "Doug Sauder" <doug@example.com>
To: "Jürgen Schmürgen" <schmuergen@example.com>
Subject: Die Hasen und die Frösche (Microsoft Outlook 00)
Date: Wed, 17 May 2000 19:10:31 -0400
Message-ID: <NDBBIAKOPKHFGPLCODIGMEKBCHAA.doug@example.com>
MIME-Version: 1.0
Content-Type: text/plain;
charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable
X-Priority: 3 (Normal)
X-MSMail-Priority: Normal
X-Mailer: Microsoft Outlook IMO, Build 9.0.2416 (9.0.2910.0)
Importance: Normal
X-MimeOLE: Produced By Microsoft MimeOLE V5.00.2314.1300
Die Hasen und die Fr=F6sche
Die Hasen klagten einst =FCber ihre mi=DFliche Lage; "wir leben", sprach =
ein Redner, "in steter Furcht vor Menschen und Tieren, eine Beute der =
Hunde, der Adler, ja fast aller Raubtiere! Unsere stete Angst ist =
=E4rger als der Tod selbst. Auf, la=DFt uns ein f=FCr allemal sterben."=20
In einem nahen Teich wollten sie sich nun ers=E4ufen; sie eilten ihm zu; =
allein das au=DFerordentliche Get=F6se und ihre wunderbare Gestalt =
erschreckte eine Menge Fr=F6sche, die am Ufer sa=DFen, so sehr, da=DF =
sie aufs schnellste untertauchten.=20
"Halt", rief nun eben dieser Sprecher, "wir wollen das Ers=E4ufen noch =
ein wenig aufschieben, denn auch uns f=FCrchten, wie ihr seht, einige =
Tiere, welche also wohl noch ungl=FCcklicher sein m=FCssen als wir."=20

View file

@ -0,0 +1,30 @@
From: "Doug Sauder" <doug@example.com>
To: "Jürgen Schmürgen" <schmuergen@example.com>
Subject: Die Hasen und die Frösche (Microsoft Outlook 00)
Date: Wed, 17 May 2000 19:11:50 -0400
Message-ID: <NDBBIAKOPKHFGPLCODIGAEKCCHAA.doug@example.com>
MIME-Version: 1.0
Content-Type: text/plain;
charset="iso-8859-1"
Content-Transfer-Encoding: base64
X-Priority: 3 (Normal)
X-MSMail-Priority: Normal
X-Mailer: Microsoft Outlook IMO, Build 9.0.2416 (9.0.2910.0)
Importance: Normal
X-MimeOLE: Produced By Microsoft MimeOLE V5.00.2314.1300
RGllIEhhc2VuIHVuZCBkaWUgRnL2c2NoZQ0KDQpEaWUgSGFzZW4ga2xhZ3RlbiBlaW5zdCD8YmVy
IGlocmUgbWnfbGljaGUgTGFnZTsgIndpciBsZWJlbiIsIHNwcmFjaCBlaW4gUmVkbmVyLCAiaW4g
c3RldGVyIEZ1cmNodCB2b3IgTWVuc2NoZW4gdW5kIFRpZXJlbiwgZWluZSBCZXV0ZSBkZXIgSHVu
ZGUsIGRlciBBZGxlciwgamEgZmFzdCBhbGxlciBSYXVidGllcmUhIFVuc2VyZSBzdGV0ZSBBbmdz
dCBpc3Qg5HJnZXIgYWxzIGRlciBUb2Qgc2VsYnN0LiBBdWYsIGxh33QgdW5zIGVpbiBm/HIgYWxs
ZW1hbCBzdGVyYmVuLiIgDQoNCkluIGVpbmVtIG5haGVuIFRlaWNoIHdvbGx0ZW4gc2llIHNpY2gg
bnVuIGVyc+R1ZmVuOyBzaWUgZWlsdGVuIGlobSB6dTsgYWxsZWluIGRhcyBhdd9lcm9yZGVudGxp
Y2hlIEdldPZzZSB1bmQgaWhyZSB3dW5kZXJiYXJlIEdlc3RhbHQgZXJzY2hyZWNrdGUgZWluZSBN
ZW5nZSBGcvZzY2hlLCBkaWUgYW0gVWZlciBzYd9lbiwgc28gc2VociwgZGHfIHNpZSBhdWZzIHNj
aG5lbGxzdGUgdW50ZXJ0YXVjaHRlbi4gDQoNCiJIYWx0IiwgcmllZiBudW4gZWJlbiBkaWVzZXIg
U3ByZWNoZXIsICJ3aXIgd29sbGVuIGRhcyBFcnPkdWZlbiBub2NoIGVpbiB3ZW5pZyBhdWZzY2hp
ZWJlbiwgZGVubiBhdWNoIHVucyBm/HJjaHRlbiwgd2llIGlociBzZWh0LCBlaW5pZ2UgVGllcmUs
IHdlbGNoZSBhbHNvIHdvaGwgbm9jaCB1bmds/GNrbGljaGVyIHNlaW4gbfxzc2VuIGFscyB3aXIu
IiANCg==

View file

@ -0,0 +1,31 @@
From: "Doug Sauder" <doug@example.com>
To: =?iso-8859-1?B?SvxyZ2VuIFNjaG38cmdlbg==?= <schmuergen@example.com>
Subject: =?iso-8859-1?Q?Die_Hasen_und_die_Fr=F6sche_=28Microsoft_Outlook_00=29?=
Date: Wed, 17 May 2000 19:13:51 -0400
Message-ID: <NDBBIAKOPKHFGPLCODIGEEKCCHAA.doug@example.com>
MIME-Version: 1.0
Content-Type: text/plain;
charset="iso-8859-1"
Content-Transfer-Encoding: 8bit
X-Priority: 3 (Normal)
X-MSMail-Priority: Normal
X-Mailer: Microsoft Outlook IMO, Build 9.0.2416 (9.0.2910.0)
Importance: Normal
X-MimeOLE: Produced By Microsoft MimeOLE V5.00.2314.1300
Die Hasen und die Frösche
Die Hasen klagten einst über ihre mißliche Lage; "wir leben", sprach ein
Redner, "in steter Furcht vor Menschen und Tieren, eine Beute der Hunde, der
Adler, ja fast aller Raubtiere! Unsere stete Angst ist ärger als der Tod
selbst. Auf, laßt uns ein für allemal sterben."
In einem nahen Teich wollten sie sich nun ersäufen; sie eilten ihm zu;
allein das außerordentliche Getöse und ihre wunderbare Gestalt erschreckte
eine Menge Frösche, die am Ufer saßen, so sehr, daß sie aufs schnellste
untertauchten.
"Halt", rief nun eben dieser Sprecher, "wir wollen das Ersäufen noch ein
wenig aufschieben, denn auch uns fürchten, wie ihr seht, einige Tiere,
welche also wohl noch unglücklicher sein müssen als wir."

View file

@ -0,0 +1,31 @@
From: "Doug Sauder" <doug@example.com>
To: =?iso-8859-1?B?SvxyZ2VuIFNjaG38cmdlbg==?= <schmuergen@example.com>
Subject: =?iso-8859-1?Q?Die_Hasen_und_die_Fr=F6sche_=28Microsoft_Outlook_00=29?=
Date: Wed, 17 May 2000 19:15:35 -0400
Message-ID: <NDBBIAKOPKHFGPLCODIGIEKCCHAA.doug@example.com>
MIME-Version: 1.0
Content-Type: text/plain;
charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable
X-Priority: 3 (Normal)
X-MSMail-Priority: Normal
X-Mailer: Microsoft Outlook IMO, Build 9.0.2416 (9.0.2910.0)
Importance: Normal
X-MimeOLE: Produced By Microsoft MimeOLE V5.00.2314.1300
Die Hasen und die Fr=F6sche
Die Hasen klagten einst =FCber ihre mi=DFliche Lage; "wir leben", sprach =
ein Redner, "in steter Furcht vor Menschen und Tieren, eine Beute der =
Hunde, der Adler, ja fast aller Raubtiere! Unsere stete Angst ist =
=E4rger als der Tod selbst. Auf, la=DFt uns ein f=FCr allemal sterben."=20
In einem nahen Teich wollten sie sich nun ers=E4ufen; sie eilten ihm zu; =
allein das au=DFerordentliche Get=F6se und ihre wunderbare Gestalt =
erschreckte eine Menge Fr=F6sche, die am Ufer sa=DFen, so sehr, da=DF =
sie aufs schnellste untertauchten.=20
"Halt", rief nun eben dieser Sprecher, "wir wollen das Ers=E4ufen noch =
ein wenig aufschieben, denn auch uns f=FCrchten, wie ihr seht, einige =
Tiere, welche also wohl noch ungl=FCcklicher sein m=FCssen als wir."=20

View file

@ -0,0 +1,34 @@
From: "Doug Sauder" <doug@example.com>
To: "Joe Blow" <jblow@example.com>,
=?utf-7?B?SitBUHctcmdlbiBTY2htK0FQdy1yZ2Vu?= <schmuergen@example.com>
Subject: =?utf-7?Q?Die_Hasen_und_die_Fr+APY-sche_=28Microsoft_Outlook_00=29?=
Date: Wed, 17 May 2000 19:18:39 -0400
Message-ID: <NDBBIAKOPKHFGPLCODIGMEKCCHAA.doug@example.com>
MIME-Version: 1.0
Content-Type: text/plain;
charset="utf-7"
Content-Transfer-Encoding: quoted-printable
X-Priority: 3 (Normal)
X-MSMail-Priority: Normal
X-Mailer: Microsoft Outlook IMO, Build 9.0.2416 (9.0.2910.0)
Importance: Normal
X-MimeOLE: Produced By Microsoft MimeOLE V5.00.2314.1300
Die Hasen und die Fr+APY-sche
Die Hasen klagten einst +APw-ber ihre mi+AN8-liche Lage+ADs- +ACI-wir =
leben+ACI-, sprach ein Redner, +ACI-in steter Furcht vor Menschen und =
Tieren, eine Beute der Hunde, der Adler, ja fast aller Raubtiere+ACE- =
Unsere stete Angst ist +AOQ-rger als der Tod selbst. Auf, la+AN8-t uns =
ein f+APw-r allemal sterben.+ACI-=20
In einem nahen Teich wollten sie sich nun ers+AOQ-ufen+ADs- sie eilten =
ihm zu+ADs- allein das au+AN8-erordentliche Get+APY-se und ihre =
wunderbare Gestalt erschreckte eine Menge Fr+APY-sche, die am Ufer =
sa+AN8-en, so sehr, da+AN8- sie aufs schnellste untertauchten.=20
+ACI-Halt+ACI-, rief nun eben dieser Sprecher, +ACI-wir wollen das =
Ers+AOQ-ufen noch ein wenig aufschieben, denn auch uns f+APw-rchten, wie =
ihr seht, einige Tiere, welche also wohl noch ungl+APw-cklicher sein =
m+APw-ssen als wir.+ACI-=20

Some files were not shown because too many files have changed in this diff Show more