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/FakeIMAPClient.php | 9 + apps/admin/lib/imap/IMAPClient.php | 498 ++++++++++++++++++++++++++ apps/admin/lib/imap/IMAPManager.php | 48 +++ apps/admin/lib/imap/IMAPServerInformation.php | 63 ++++ apps/admin/lib/myChangeNicknameValidator.php | 43 +++ apps/admin/lib/myDenyStringValidator.php | 38 ++ apps/admin/lib/myLoginValidator.php | 45 +++ apps/admin/lib/myUser.class.php | 83 +++++ 8 files changed, 827 insertions(+) create mode 100644 apps/admin/lib/imap/FakeIMAPClient.php create mode 100644 apps/admin/lib/imap/IMAPClient.php create mode 100644 apps/admin/lib/imap/IMAPManager.php create mode 100644 apps/admin/lib/imap/IMAPServerInformation.php create mode 100644 apps/admin/lib/myChangeNicknameValidator.php create mode 100644 apps/admin/lib/myDenyStringValidator.php create mode 100644 apps/admin/lib/myLoginValidator.php create mode 100644 apps/admin/lib/myUser.class.php (limited to 'apps/admin/lib') diff --git a/apps/admin/lib/imap/FakeIMAPClient.php b/apps/admin/lib/imap/FakeIMAPClient.php new file mode 100644 index 0000000..aad0207 --- /dev/null +++ b/apps/admin/lib/imap/FakeIMAPClient.php @@ -0,0 +1,9 @@ + 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); + } +} + +?> diff --git a/apps/admin/lib/imap/IMAPManager.php b/apps/admin/lib/imap/IMAPManager.php new file mode 100644 index 0000000..0cac78a --- /dev/null +++ b/apps/admin/lib/imap/IMAPManager.php @@ -0,0 +1,48 @@ +openConnection( + $imap_settings['host'].":". + $imap_settings['port'] + ); + + $success = self::$adminclient->login( + $imap_settings['admin'], + $imap_settings['pass'] + ); + + if(!$success) + throw new Exception("Failed to authenticate on IMAP server."); + + } catch (Exception $e) { + LogEntryPeer::log($e->getMessage(), LogEntry::PRIO_ALERT); + } + + return self::$adminclient; + } + + public static function getAdminUsername() + { + $server_settings = sfConfig::get('app_server_default'); + $imap_settings = $server_settings['imap']; + return $imap_settings['admin']; + } +} + +?> diff --git a/apps/admin/lib/imap/IMAPServerInformation.php b/apps/admin/lib/imap/IMAPServerInformation.php new file mode 100644 index 0000000..e185bae --- /dev/null +++ b/apps/admin/lib/imap/IMAPServerInformation.php @@ -0,0 +1,63 @@ +imap = IMAPManager::getAdminConnection(); + } + + public function getGreeting() + { + return substr($this->imap->getGreeting(), 5); + } + + public function getServerName() + { + return $this->imap->getServerName(); + } + + public function getServerVersion() + { + return $this->imap->getServerVersion(); + } + + public function hasCapability($name) + { + return $this->imap->hasCapability($name); + } + + public function getCapabilties() + { + if(!($c = $this->imap->capability())) + $c = array(); + return $c; + } + + public function getHierarchyDelimiter() + { + return $this->imap->getHierarchyDelimiter(); + } + + public function getAvailableACL() + { + if(!$this->imap->hasCapability('ACL')) + return false; + + return $this->imap->getAvailableACL(); + } + + public function getHost() + { + return substr($this->imap->getURI(), 0, strpos($this->imap->getURI(), ":")); + } + + public function getPort() + { + return substr($this->imap->getURI(), strpos($this->imap->getURI(), ":")+1); + } +} + +?> diff --git a/apps/admin/lib/myChangeNicknameValidator.php b/apps/admin/lib/myChangeNicknameValidator.php new file mode 100644 index 0000000..e8dfdb1 --- /dev/null +++ b/apps/admin/lib/myChangeNicknameValidator.php @@ -0,0 +1,43 @@ +setParameter('change_error', 'Invalid input'); + + $this->getParameterHolder()->add($parameters); + + return true; + } + + public function execute(&$value, &$error) + { + $current_nickname = $this->getContext()->getUser()->getNickname(); + + $new_nickname = $value; + + // changed the nickname? + if($new_nickname == $current_nickname) + return true; + + $c = new Criteria(); + $c->add(UserPeer::NICKNAME, $new_nickname); + $user = UserPeer::doSelectOne($c); + + // nickname exists? + if($user) + { + $error = $this->getParameter('change_error'); + return false; + } + + return true; + } +} + +?> diff --git a/apps/admin/lib/myDenyStringValidator.php b/apps/admin/lib/myDenyStringValidator.php new file mode 100644 index 0000000..d817ea9 --- /dev/null +++ b/apps/admin/lib/myDenyStringValidator.php @@ -0,0 +1,38 @@ +setParameter('values_error', 'Invalid input'); + + $this->getParameterHolder()->add($parameters); + + return true; + } + + public function execute(&$value, &$error) + { + $values = $this->getParameter('values'); + + if(!is_array($values)) + return true; + + foreach($values as $check) + { + if($check==$value) + { + $error = $this->getParameter('values_error'); + return false; + } + } + + return true; + } +} + +?> diff --git a/apps/admin/lib/myLoginValidator.php b/apps/admin/lib/myLoginValidator.php new file mode 100644 index 0000000..03890aa --- /dev/null +++ b/apps/admin/lib/myLoginValidator.php @@ -0,0 +1,45 @@ +setParameter('login_error', 'Invalid input'); + + $this->getParameterHolder()->add($parameters); + + return true; + } + + public function execute(&$value, &$error) + { + $password_param = $this->getParameter('password'); + $password = $this->getContext()->getRequest()->getParameter($password_param); + + $login = $value; + + $c = new Criteria(); + $c->add(UserPeer::NICKNAME, $login); + $user = UserPeer::doSelectOne($c); + + // nickname exists? + if ($user) + { + // password is OK? + if (sha1($user->getSalt().$password) == $user->getSha1Password()) + { + $this->getContext()->getUser()->signIn($user); + return true; + } + } + + $error = $this->getParameter('login_error'); + return false; + } +} + +?> diff --git a/apps/admin/lib/myUser.class.php b/apps/admin/lib/myUser.class.php new file mode 100644 index 0000000..2f67e51 --- /dev/null +++ b/apps/admin/lib/myUser.class.php @@ -0,0 +1,83 @@ +setAuthenticated(true); + + // TODO: set credentials + $this->addCredential($user->getRole()->getCredentials()); + + // set session attributes + $this->updateUserAttributes($user); + + $this->setResultsPerPage(sfConfig::get("app_pagination_results_per_page")); + + LogEntryPeer::log("User logged in.", LogEntry::PRIO_INFO); + + $user->setLastlogin(time()); + $user->save(); + } + + public function updateUserAttributes($user) + { + $this->setAttribute("nickname", $user->getNickname(), "user"); + $this->setAttribute("role", $user->getRole()->getName(), "user"); + $this->setAttribute("role_id", $user->getRole()->getId(), "user"); + $this->setAttribute("user_id", $user->getId(), "user"); + $this->setAttribute("lastlogin", $user->getLastLogin(), "user"); + } + + public function signOut($user) + { + LogEntryPeer::log("User logged out.", LogEntry::PRIO_INFO); + + $this->setAuthenticated(false); + $this->clearCredentials(); + $this->getAttributeHolder()->removeNamespace("user"); + $this->getAttributeHolder()->removeNamespace("pager"); + } + + public function getResultsPerPage() + { + return $this->getAttribute("max_per_page", sfConfig::get("app_pager_max_per_page"), "pager"); + } + + + public function setResultsPerPage($count) + { + $this->setAttribute("max_per_page", $count, "pager"); + } + + public function getDomainPermissions($c = null) + { + $user = UserPeer::retrieveByPK($this->getId()); + return $user->getDomainPermissions($c); + } + + public function getId() + { + return $this->getAttribute("user_id", '', "user"); + } + + public function getLastLogin() + { + return $this->getAttribute("lastlogin", '', "user"); + } + + public function getRoleId() + { + return $this->getAttribute("role_id", '', "user"); + } + + public function getRoleName() + { + return $this->getAttribute("role", '', "user"); + } + + public function getNickname() + { + return $this->getAttribute("nickname", '', "user"); + } +} -- cgit v1.1-32-gdbae