# -*- coding: utf-8 # Copyright 2008 by Vahur Rebas from zope.interface import Interface from Products.PluggableAuthService.interfaces.plugins import IAuthenticationPlugin from Products.PluggableAuthService.interfaces.plugins import IUserEnumerationPlugin from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin from Products.PageTemplates.PageTemplateFile import PageTemplateFile from AccessControl import ClassSecurityInfo, AuthEncoding from BTrees.OOBTree import OOBTree from OFS.SimpleItem import SimpleItem from Products.PluggableAuthService.utils import classImplements import time import re import socket import sha CONNECTSTART = [0, 1, re.compile(".*?\+OK(.*)", re.DOTALL)] SENDUSERNAME = [0, 1, re.compile("(.*)", re.DOTALL)] SENDPASSWORD = [0, 1, re.compile(".*?\+OK(.*)", re.DOTALL)] PASSWORDOK = [0, 1, re.compile(".*?\+OK(.*)", re.DOTALL)] PASSWORDNOTOKAY = [0, 1, re.compile(".*?-ERR(.*)", re.DOTALL)] OKAYTOSENDAGAIN = [0, 1, re.compile("(.*)", re.DOTALL)] ERROR = [0, 1, re.compile(".*?-ERR(.*)", re.DOTALL)] USERNAME = "USER %s\n" PASSWORD = "PASS %s\n" AFTER = "QUIT" import logging logger = logging.getLogger('IVA') from config import USERNAMES_FILE, USERS_FROM_POP _SYSTEM_NAMES = [] try: f = open(USERNAMES_FILE, 'r') ll = f.readlines() f.close() [_SYSTEM_NAMES.append(x.strip()) for x in ll if x] except IOError: pass class IPOP3Plugin(Interface): """ POP3 Plugin Interface """ manage_addPOP3ManagerForm = PageTemplateFile( 'addpop3', globals(), __name__='manage_addZODBUserManagerForm' ) def addPOP3Plugin(dispatcher, id, host, port, user_cache, bad_user_cache, timeout, title=None, REQUEST=None): """ add pop3 plugin """ pop = POP3Plugin(id, title, host, port, user_cache, timeout, bad_user_cache) dispatcher._setObject(pop.getId(), pop) if REQUEST is not None: REQUEST['RESPONSE'].redirect('%s/manage_workspace?manage_tabs_message=POP3+Authenticator+added.' % dispatcher.absolute_url()) class POP3Plugin( BasePlugin ): """ PAS POP3 plugin """ meta_type = "POP3 Authenticator" security = ClassSecurityInfo() manage_options = ( ( { 'label': 'Cache', 'action': 'manage_cache', }, ) + BasePlugin.manage_options ) def __init__(self, id, title, host, port, user_cache, timeout, bad_user_cache): self._id = self.id = id self.title = title self.host = str(host) self.port = int(port) self.user_cache = int(user_cache) self.timeout = int(timeout) self.bad_user_cache = int(bad_user_cache) # # IAuthenticationPlugin implementation # security.declarePrivate( 'authenticateCredentials' ) def authenticateCredentials( self, credentials ): """See IAuthenticationPlugin. o We expect the credentials to be those returned by ILoginPasswordExtractionPlugin. """ login = credentials.get( 'login' ) password = credentials.get( 'password' ) if login is None or login == "" or password is None or password == "": return None cached = self._checkGoods(login, password) if cached: return (login, login) cached = self._checkBad(login, password) if cached: return None valid = self._remoteAuth(login, password) if valid == True: self._cacheGood(login, password) return (login, login) elif valid == -1: # bad username/password self._cacheBad(login, password) return None else: # False # internal error? pass return None # # IUserEnumerationPlugin implementation # def enumerateUsers(self, id=None, login=None, exact_match=None, sort_by=None, max_results=None, **kw): """ IUserEnumerationPlugin """ #print "POP3: enumerateUsers" #print "id:", id #print "login:", login #print "exact_match:", exact_match #print "sort_by:", sort_by #print "max_results:",max_results #print "kw:", kw plugin_id = self.getId() terms = id or login if exact_match: if USERS_FROM_POP: # True if terms in _SYSTEM_NAMES: return [{'id':terms, 'login':terms, 'pluginid':plugin_id}] else: return () else: # False - users listed don't come from POP3 if terms in _SYSTEM_NAMES: return () else: return [{'id':terms, 'login':terms, 'pluginid':plugin_id}] else: print "NOT exact_match" def _getGoodsCacher(self): _marker = object() tmp = getattr(self, 'temp_folder', _marker) cm = getattr(tmp, 'popgoodcache', _marker) if cm is _marker: tmp._setObject('popgoodcache', POP3Cache('popgoodcache')) cm = getattr(tmp, 'popgoodcache', _marker) return cm def _getBadsCacher(self): _marker = object() tmp = getattr(self, 'temp_folder', _marker) cm = getattr(tmp, 'popbadcache', _marker) if cm is _marker: tmp._setObject('popbadcache', POP3Cache('popbadcache')) cm = getattr(tmp, 'popbadcache', _marker) return cm def _checkGoods(self, username, password): """ check cache """ cm = self._getGoodsCacher() return cm._checkCache(username, password, self.user_cache) def _checkBad(self, username, password): """ check cache """ cm = self._getBadsCacher() return cm._checkCache(username, password, self.bad_user_cache) def _cacheGood(self, username, password): """ cache user """ cm = self._getGoodsCacher() cm._doCache(username, password) def _cacheBad(self, username, password): cm = self._getBadsCacher() cm._doCache(username, password) def _getSocket(self): """ Open a socket """ rsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) logger.debug( "connecting to %s %s" % (self.host, self.port) ) rsocket.connect( (self.host, self.port) ) return rsocket def _remoteAuth(self, username, password): """ connect to POP3 server and login """ thesocket = self._getSocket() data = thesocket.recv(1) connectionstarted = 0 sendusername = 0 sendpassword = 0 start_time = time.time() while data and time.time() < start_time + self.timeout: #logger.debug("Data = %s" % (data) ) if connectionstarted: if not sendusername: # Check if it's okay to send the username matches = SENDUSERNAME[2].match(data) if matches: data = matches.group(SENDUSERNAME[1]) sendusername = 1 #logger.info("sending: %s" % (USERNAME % (username)) ) thesocket.send(USERNAME % (username)) elif not sendpassword: # Check if it's okay to send the password matches = SENDPASSWORD[2].match(data) if matches: data = matches.group(SENDPASSWORD[1]) sendpassword = 1 #logger.info("sending: %s" % (PASSWORD % (password)) ) thesocket.send(PASSWORD % (password)) else: # Wait for okay or not okay matches = PASSWORDOK[2].match(data) if matches: data = matches.group(PASSWORDOK[1]) thesocket.send(AFTER) #self.finishUp(data, thesocket, username) return True matches = PASSWORDNOTOKAY[2].match(data) if matches: data = matches.group(PASSWORDNOTOKAY[1]) thesocket.send(AFTER) # wrong username/password, cache that! return -1 else: # Check if the connection has started matches = CONNECTSTART[2].match(data) if matches: data = matches.group(CONNECTSTART[1]) connectionstarted = 1 # Check for an error matches = ERROR[2].match(data) if matches: logger.debug("Error. line 247") return False data = data + thesocket.recv(1) logger.info("Timeout!") return None classImplements(POP3Plugin, IPOP3Plugin, IAuthenticationPlugin, IUserEnumerationPlugin) from App.class_init import default__class_init__ as InitializeClass InitializeClass(POP3Plugin) class POP3Cache(SimpleItem): """ Cache of good users """ meta_type = "POP3 User cache" def __init__(self, id): self.id = id self._cache = OOBTree() def _checkCache(self, username, password, user_cache): uname = sha.new(username+'::'+password).hexdigest() if not self._cache.has_key(uname): return None start = self._cache.get(uname) curr = time.time() if start+user_cache > curr: return True return False def _doCache(self, username, password): """ cache user """ uname = sha.new(username+'::'+password).hexdigest() u = {uname: time.time()} self._cache.update(u)