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