summaryrefslogtreecommitdiffstats
path: root/apps/admin/lib/imap/IMAPClient.php
diff options
context:
space:
mode:
Diffstat (limited to 'apps/admin/lib/imap/IMAPClient.php')
-rw-r--r--apps/admin/lib/imap/IMAPClient.php498
1 files changed, 498 insertions, 0 deletions
diff --git a/apps/admin/lib/imap/IMAPClient.php b/apps/admin/lib/imap/IMAPClient.php
new file mode 100644
index 0000000..2271cc9
--- /dev/null
+++ b/apps/admin/lib/imap/IMAPClient.php
@@ -0,0 +1,498 @@
+<?php
+
+// Hello. Let's re-implement as we only need basic stuff...
+// Could not find a suitable OO IMAP client class library either.
+
+class IMAPClient
+{
+ const RESULT_OK = 'OK';
+ const RESULT_NO = 'NO';
+ const RESULT_BAD = 'BAD';
+
+ const STATE_DISCONNECTED = 0;
+ const STATE_GREETING = 1;
+ const STATE_UNAUTH = 2;
+ const STATE_AUTH = 3;
+ const STATE_SELECTED = 4;
+
+ // connection
+ protected $state = self::STATE_DISCONNECTED;
+
+ private $socket = null;
+ public function __destruct()
+ {
+ // connection vanishes with instance
+ if($this->socket)
+ $this->closeConnection();
+ }
+
+ // FIXME: Extract server name and version; should probably be moved to own library (?)
+ /*
+ Couple of banners:
+ * OK mail.mirapoint.com Mirapoint IMAP4 3.7.4-GA server ready
+ * OK [CAPABILITY IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT QUOTA IDLE ACL ACL2=UNION STARTTLS] Courier-IMAP ready. Copyright 1998-2004 Double Precision, Inc. See COPYING for distribution information.
+ * OK mail8.hotbox.ru Cyrus IMAP4 v2.2.3 server ready
+ * OK dovecot ready.
+ * OK Microsoft Exchange IMAP4rev1 server version 5.5.2654.50 (wlvexc00.diginsite.com) ready
+ * OK Microsoft Exchange Server 2003 IMAP4rev1 server version 6.5.6944.0 (stbowa02.stb.sun.ac.za) ready.
+ * OK [x.x.x.x] IMAP4rev1 Mercury/32 v4.01a server ready.
+ * OK Messaging Multiplexor (iPlanet Messaging Server 5.2 HotFix 1.26 (built Mar 31 2004))
+ * OK [CAPABILITY IMAP4REV1 LOGIN-REFERRALS STARTTLS AUTH=LOGIN] leto IMAP4rev1 2001.315rh at Mon, 31 May 2004 11:06:55 +0200 (CEST)
+ * OK mail1 Cyrus IMAP4 v2.0.16 server ready
+ * OK Microsoft Exchange IMAP4rev1 server version Exchange 2000.4208.3 (EX2000.com) ready.
+ * OK mcis.com Microsoft IMAP4Rev1 Server ready at Thu, 16 Mar 2000 19:03:15 -0800 Version: 5.5.1877.377.37
+ */
+
+ // list of (name, version) regex pairs, first match has to be the value
+ protected static $servers = array(
+ array("/(Cyrus\sIMAP)4/", "/Cyrus\sIMAP4\sv((\d+)\.(\d+).(\d+))/"),
+ array("/(Courier.IMAP)/", null),
+ );
+
+ public function getServerName()
+ {
+ foreach(self::$servers as $server)
+ {
+ if(preg_match($server[0], $this->greeting, $matches))
+ {
+ return $matches[1];
+ }
+ }
+
+ return false;
+ }
+
+ public function getServerVersion()
+ {
+ foreach(self::$servers as $server)
+ {
+ if(preg_match($server[0], $this->greeting, $matches))
+ {
+ if($server[1] == null)
+ return "Unknown";
+
+ if(preg_match($server[1], $this->greeting, $matches))
+ {
+ return $matches[1];
+ }
+ }
+ }
+
+ return false;
+ }
+
+ protected $greeting = null;
+ public function getGreeting()
+ {
+ return $this->greeting;
+ }
+
+ protected $uri = null;
+ public function getURI()
+ {
+ return $this->uri;
+ }
+
+ public function openConnection($uri, $timeout = 10)
+ {
+ $this->resetClient();
+ $this->socket = $this->socket = fsockopen($this->uri = $uri, null, $errno, $errstr);
+ if(!$this->socket)
+ throw new Exception('Could not open Socket for IMAP connection: "'.$errstr.'"', $errno);
+ $this->state = self::STATE_GREETING;
+ $this->greeting = fgets($this->socket, 1024);
+
+ if (sfConfig::get('sf_logging_enabled'))
+ {
+ sfContext::getInstance()->getLogger()->info("{IMAPClient} Greeting: ".trim($this->greeting));
+ }
+
+ $this->state = self::STATE_UNAUTH;
+
+ return true;
+ }
+
+ public function closeConnection()
+ {
+ if($this->socket)
+ {
+ if(!$this->logout())
+ throw new Exception("Logout error.");
+ fclose($this->socket);
+ }
+ $this->resetClient();
+ return true;
+ }
+
+ protected function resetClient()
+ {
+ $this->state = self::STATE_GREETING;
+ $this->capabilities = null;
+ $this->hierarchydelimiter = null;
+ }
+
+ protected function sendCommand($tag, $cmd, $args = "")
+ {
+ // check if we got a connection
+ if(!$this->socket)
+ throw new Exception('Unable to execute "'.$cmd.'" command: Not connected to a server.');
+
+ if(is_array($args))
+ {
+ $args = " \"" . implode("\" \"", $args) . "\"";
+ }
+
+ if (sfConfig::get('sf_logging_enabled'))
+ {
+ sfContext::getInstance()->getLogger()->info("{IMAPClient} Command: ".$tag." ".$cmd.$args);
+ }
+
+ //LogEntryPeer::log("{IMAPClient} Command: ".$tag." ".$cmd.$args, LogEntry::PRIO_DEBUG);
+
+ // send command
+ fputs($this->socket, $tag." ".$cmd.$args."\n");
+
+ // read response
+ $response = array();
+ do
+ {
+ $line = fgets($this->socket, 1024);
+ $response[] = $line;
+
+ if (sfConfig::get('sf_logging_enabled'))
+ {
+ sfContext::getInstance()->getLogger()->info("{IMAPClient} Line: \"".trim($line)."\"");
+ }
+ //LogEntryPeer::log("{IMAPClient} Line: \"".trim($line)."\"", LogEntry::PRIO_DEBUG);
+ }
+ while(self::getTag($line) != $tag && !feof($this->socket));
+
+ return $response;
+ }
+
+ // PARSER API
+ protected static $tagcounter = 0;
+ protected static function generateTag()
+ {
+ $pattern = "0123456789".
+ "abcdefghij".
+ "klmnopqrst".
+ "uvwxyz";
+
+ if(self::$tagcounter==0)
+ self::$tagcounter = rand(0,999999);
+
+ $s = "";
+ $r = self::$tagcounter++;
+ do
+ {
+ $m = $r % 36;
+ $s = $pattern{$m} . $s;
+ }
+ while(($r = ($r-$m) / 36) > 0);
+ return sprintf("%04s", $s);
+ }
+
+ protected static function getTag($line)
+ {
+ return self::getResponseData($line, 0);
+ }
+
+ protected static function getResponseData($line, $index = 0)
+ {
+ $tokens = explode(" ", $line);
+ return $tokens[$index];
+ }
+
+ protected static function getResultStatus($tag, $response)
+ {
+ if(is_array($response))
+ {
+ foreach($response as $line)
+ {
+ if(self::getTag($line) == $tag)
+ {
+ return self::getResponseData($line, 1);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ protected function runCommandAndGetResult($command, $arguments = "")
+ {
+ $tag = self::generateTag();
+ $response = $this->sendCommand($tag, $command, $arguments);
+ return self::getResultStatus($tag, $response);
+ }
+
+ // IMAP API
+
+ // RFC 3501 - IMAP4rev1
+ // RFC 1730 - IMAP4
+ public function login($user, $password)
+ {
+ $result = $this->runCommandAndGetResult("login", array($user, $password));
+ if($result == self::RESULT_OK)
+ $this->state = self::STATE_AUTH;
+
+ return ($result == self::RESULT_OK);
+ }
+
+ public function logout()
+ {
+ $result = $this->runCommandAndGetResult("logout");
+ if($result == self::RESULT_OK)
+ $this->state = self::STATE_UNAUTH;
+
+ return ($result == self::RESULT_OK);
+ }
+
+ public function noop()
+ {
+ $result = $this->runCommandAndGetResult("noop");
+ return ($result == self::RESULT_OK);
+ }
+
+ protected $hierarchydelimiter = null;
+
+ public function getHierarchyDelimiter()
+ {
+ if($this->hierarchydelimiter != null)
+ return $this->hierarchydelimiter;
+
+ if($info = $this->getlist('', ''))
+ {
+ $this->hierarchydelimiter = $info[0]['delimiter'];
+ return $this->hierarchydelimiter;
+ }
+
+ return $info;
+ }
+
+ // API break as "list" is PHP keyword...
+ public function getlist($reference = '', $mailboxname = '*')
+ {
+ $tag = self::generateTag();
+ $response = $this->sendCommand($tag, "list", array($reference, $mailboxname));
+ $result = self::getResultStatus($tag, $response);
+
+ if($result == self::RESULT_OK)
+ {
+ // unserialize response
+ $list = array();
+
+ foreach($response as $line)
+ {
+ if(preg_match('/\*\sLIST\s\((.*)\)\s\"(.*?)\"\s\"(.*?)\"/', $line, $matches))
+ {
+ $list[] = array('attributes' => $matches[1],
+ 'delimiter' => $matches[2],
+ 'name' => $matches[3]
+ );
+ }
+ }
+
+ return $list;
+ }
+
+ return false;
+ }
+
+ protected $capabilities = null;
+
+ public function hasCapability($id)
+ {
+ if($this->capabilities == null)
+ $this->capability();
+
+ foreach($this->capabilities as $capability)
+ {
+ if($id == $capability)
+ return true;
+ }
+ return false;
+ }
+
+ public function capability()
+ {
+ // cache this call
+ if($this->capabilities)
+ return $this->capabilities;
+
+ $tag = self::generateTag();
+ $response = $this->sendCommand($tag, "capability");
+ $result = self::getResultStatus($tag, $response);
+
+ if($result == self::RESULT_OK)
+ {
+ foreach($response as $line)
+ {
+ if(preg_match('/\*\sCAPABILITY\s(.*?)$/i', $line, $matches))
+ {
+ $this->capabilities = explode(" ", $matches[1]);
+ return $this->capabilities;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public function create($name)
+ {
+ $result = $this->runCommandAndGetResult("create", array($name));
+ return ($result == self::RESULT_OK);
+ }
+
+ public function delete($name)
+ {
+ $result = $this->runCommandAndGetResult("delete", array($name));
+ return ($result == self::RESULT_OK);
+ }
+
+ public function rename($name, $newname)
+ {
+ $result = $this->runCommandAndGetResult("rename", array($name, $newname));
+ return ($result == self::RESULT_OK);
+ }
+
+ // RFC 2087 - QUOTA (Extension)
+ const QUOTA_TYPE_STORAGE = "STORAGE";
+ const QUOTA_TYPE_MESSAGE = "MESSAGE";
+
+ public function getquota($quotaroot)
+ {
+ $tag = self::generateTag();
+ $response = $this->sendCommand($tag, "getquota", array($quotaroot));
+ $result = self::getResultStatus($tag, $response);
+
+ if($result == self::RESULT_OK)
+ {
+ foreach($response as $line)
+ {
+ if(preg_match('/\*\sQUOTA\s'.$quotaroot.'\s\((\w)*\s(\d+)\s(\d+)\)/i', $line, $matches))
+ {
+ return array(
+ 'resource' => $matches[1],
+ 'current' => $matches[2],
+ 'max' => $matches[3],
+ );
+ }
+ }
+ }
+
+ // OK -> not set, NO -> not set
+ if($result != self::RESULT_BAD)
+ {
+ return array(
+ 'resource' => null,
+ 'current' => null,
+ 'max' => null
+ );
+ }
+
+ return false;
+ }
+
+ public function setquota($quotaroot, $quota = null, $resource = self::QUOTA_TYPE_STORAGE)
+ {
+ $tag = self::generateTag();
+ $args = ' "'.$quotaroot.'" (';
+ if($quota == 0 || $quota == null)
+ $args .= ')';
+ else
+ $args .= $resource.' '.intval($quota).')';
+ $response = $this->sendCommand($tag, "setquota", $args);
+ $result = self::getResultStatus($tag, $response);
+
+ return ($result == self::RESULT_OK);
+ }
+
+ // RFC 4314 - ACL (Extension)
+ const ACL_NONE = '';
+ const ACL_LOOKUP = 'l';
+ const ACL_READ = 'r';
+ const ACL_SEEN = 's';
+ const ACL_WRITE = 'w';
+ const ACL_INSERT = 'i';
+ const ACL_POST = 'p';
+
+ const ACL_CREATE = 'c'; // obsolete/depreciated
+ const ACL_DELETE = 'd'; // obsolete/depreciated
+
+ const ACL_ADMIN = 'a';
+
+ const ACL_CREATEMB = 'k';
+ const ACL_DELETEMB = 'x';
+ const ACL_EXPUNGE = 'e';
+ const ACL_DELETEMSGS = 't';
+
+ public function getAvailableACL()
+ {
+ $result = self::ACL_LOOKUP.
+ self::ACL_READ.
+ self::ACL_SEEN.
+ self::ACL_WRITE.
+ self::ACL_INSERT.
+ self::ACL_POST.
+ self::ACL_CREATE.
+ self::ACL_DELETE.
+ self::ACL_ADMIN;
+
+ // add additional rights set in the capabilities
+ if($this->capabilities == null)
+ $this->capability();
+
+ foreach($this->capabilities as $capability)
+ {
+ if(preg_match('/RIGHTS=([kxte]*)/i', $capability, $matches))
+ {
+ $result .= $matches[1];
+ break;
+ }
+ }
+
+ return $result;
+ }
+
+ public function getacl($mailboxname)
+ {
+ $tag = self::generateTag();
+ $response = $this->sendCommand($tag, "getacl", array($mailboxname));
+ $result = self::getResultStatus($tag, $response);
+
+ if($result == self::RESULT_OK)
+ {
+ foreach($response as $line)
+ {
+ // grab acl list
+ if(preg_match('/\*\sACL\s[^\s]*\s(.*)/', $line, $matches))
+ {
+ // grab all acls
+ if(preg_match_all('/([^\s]*)\s(['.$this->getAvailableACL().']*)/', $matches[1], $matches))
+ {
+ return array_combine($matches[1], $matches[2]);
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public function setacl($mailboxname, $identifier, $aclmodification)
+ {
+ $result = $this->runCommandAndGetResult("setacl", array($mailboxname, $identifier, $aclmodification));
+ return ($result == self::RESULT_OK);
+ }
+
+ public function deleteacl($mailboxname, $identifier)
+ {
+ $result = $this->runCommandAndGetResult("deleteacl", array($mailboxname, $identifier));
+ return ($result == self::RESULT_OK);
+ }
+}
+
+?>