From 9f108dd1a969473375341d92a7b1252fa2cedc9a Mon Sep 17 00:00:00 2001 From: mszulecki Date: Thu, 14 Jun 2007 17:09:01 +0000 Subject: Initial import. git-svn-id: http://svn.sukimashita.com/repos/mailadmin/trunk@2 4281df72-ff29-0410-8fee-2d9ac0c5f5a7 --- apps/admin/lib/imap/IMAPClient.php | 498 +++++++++++++++++++++++++++++++++++++ 1 file changed, 498 insertions(+) create mode 100644 apps/admin/lib/imap/IMAPClient.php (limited to 'apps/admin/lib/imap/IMAPClient.php') 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 @@ +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); + } +} + +?> -- cgit v1.1-32-gdbae