From 0b8c4ac523d3934762517c2b149fcfa0920e78c5 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Thu, 31 Jan 2013 07:25:15 +0000 Subject: Separated CouchDB code into mx/couchdb.py and added fake-SMTP status codes to alias_resolver. --- src/leap/mx/alias_resolver.py | 144 +++++++++++++----------------------------- src/leap/mx/couchdb.py | 118 ++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 101 deletions(-) create mode 100644 src/leap/mx/couchdb.py diff --git a/src/leap/mx/alias_resolver.py b/src/leap/mx/alias_resolver.py index da37314..7aeeedf 100644 --- a/src/leap/mx/alias_resolver.py +++ b/src/leap/mx/alias_resolver.py @@ -14,12 +14,6 @@ Classes for resolving postfix aliases. import os import uuid -try: - from paisley import client -except ImportError: - print "This software requires paisley. Please see the README file" - print "for instructions on getting required dependencies." - try: from twisted.internet import address, defer, reactor from twisted.mail import maildir, alias @@ -43,104 +37,52 @@ def createID(alias): """ return uuid.uuid5(uuid.NAMESPACE_URL, str(alias)) - -class ConnectedCouchDB(client.CouchDB): - """ - Connect to a CouchDB instance. - - ## xxx will we need to open CouchDB documents and views? - ## yes, these are in a _design document - ## - +class StatusCodes(object): """ - def __init__(self, host, port, dbName=None, - username=None, password=None, *args, **kwargs): - """ - Connect to a CouchDB instance. - - @param host: A hostname string for the CouchDB server. - @param port: The port of the CouchDB server, as an integer. - @param dbName: (optional) The default database to connect to. - @param username: (optional) The username for authorization. - @param password: (optional) The password for authorization. - @returns: A :class:`twisted.internet.defer.Deferred` representing the - the client connection to the CouchDB instance. - """ - super(client.CouchDB, self).__init__(host, port, - dbName=dbName, - username=username, - password=password, - *args, **kwargs) - if dbName: - self.bindToDB(dbName) - else: - databases = self.listDB() - log.msg("Available databases: %s" % databases) - - def queryByEmailOrAlias(self, alias, dbDoc="User", - view="by_email_or_alias"): - """ - Check to see if a particular email or alias exists. - - @param alias: A string representing the email or alias to check. - @param dbDoc: The CouchDB document to open. - @param view: The view of the CouchDB document to use. - """ - assert isinstance(alias, str), "Email or alias queries must be string" + The Postfix manual states: - ## Prepend a forward slash, in case we forgot it: - if not alias.startswith('/'): - alias = '/' + alias + The request completion status is one of OK, RETRY, NOKEY (lookup + failed because the key was not found), BAD (malformed request) or DENY + (the table is not approved for proxy read or update access). - d = self.openDoc(dbDoc) - d.addCallbacks(self.openView, log.err, (view)) - d.addCallbacks(self.get, log.err, (alias)) - d.addCallbacks(self.parseResult, log.err) - - @d.addCallback - def show_answer(result): - log.msg("Query: %s" % alias) - log.msg("Answer: %s" % alias) - - return d - - def query(self, uri): - """ - Query a CouchDB instance that we are connected to. - """ - try: - self.checkURI(uri) ## xxx write checkURI() - ## xxx we might be able to use self._parseURI() - except SchemeNotSupported, sns: ## xxx where in paisley is this? - log.exception(sns) ## xxx need log.exception() - - d = self.get(uri) - @d.addCallback - def parse_answer(answer): - return answer - - return answer - - @defer.inlineCallbacks - def listUsersAndEmails(self, limit=1000, reverse=False): - """ - List all users and email addresses, up to the given limit. - """ - query = "/users/_design/User/_view/by_email_or_alias/?reduce=false" - answer = yield self.query(query, limit=limit, reverse=reverse) - - if answer: - parsed = yield self.parseResult(answer) - if parsed: - log.msg("%s" % parsed) - else: - log.msg("No answer from database, perhaps there are no users.") - else: - log.msg("Problem querying CouchDB instance...") - log.debug("Host: %s" % host) - log.debug("Port: %d" % port) - -class PostfixAliasResolver(postfix.PostfixTCPMapServer): + Other SMTP codes: http://www.greenend.org.uk/rjk/tech/smtpreplies.html + """ + OK = "OK Others might say 'HELLA AWESOME'...but we're not convinced." + RETRY = "RETRY Server is busy plotting revolution; requests might take a while." + BAD = "BAD bad Leroy Brown, baddest man in the whole...er. Malformed request." + NOKEY = "NOKEY Couldn't find your keys, sorry. Did you check in the sofa?" + DEFER = "DEFER_IF_LOCAL xxx fill me in" + DENY = "DENY no gurlz aloud in teh tree house." + FAIL = "FAIL xxx fill me in" + + fakeSMTPCodes = { '250': OK, + '300': RETRY, + '500': BAD, + '550': NOKEY, + '552': DEFER, + '553': DENY, + '554': FAIL, } + + def __init__(self, status_code=None): + """xxx fill me in""" + if status_code: + self.get(status_code) + + def get(self, status_code=None) + """xxx fill me in""" + if status_code: + if isinstance(status_code, str): + return status_code, getattr(self, status_code.upper(), None) + elif isinstance(status_code, int): + for k, v in self.fake_smtp_codes.items(): + ## we want to return None if it's 550 + if k == str(status_code) and k != '550': + return status_code, v + log.debug("%s" % self.NOKEY) + return None, '' + + +class AliasResolver(postfix.PostfixTCPMapServer): """ Resolve postfix aliases, similarly to using "$ postmap -q ". diff --git a/src/leap/mx/couchdb.py b/src/leap/mx/couchdb.py new file mode 100644 index 0000000..b5d4127 --- /dev/null +++ b/src/leap/mx/couchdb.py @@ -0,0 +1,118 @@ +# -*- encoding: utf-8 -*- +''' +couchdb.py +========== +Classes for working with CouchDB or BigCouch instances which store email alias +maps, user UUIDs, and GPG keyIDs. + +@authors: Isis Agora Lovecruft +@version: 0.0.1-beta +@license: see included LICENSE file +''' + +try: + from paisley import client +except ImportError: + print "This software requires paisley. Please see the README file" + print "for instructions on getting required dependencies." + +try: + from twisted.internet import defer +except ImportError: + print "This software requires Twisted. Please see the README file" + print "for instructions on getting required dependencies." + +from leap.mx.util import log + + +class ConnectedCouchDB(client.CouchDB): + """ + Connect to a CouchDB instance. + + ## xxx will we need to open CouchDB documents and views? + ## yes, these are in a _design document + """ + def __init__(self, host, port, dbName=None, username=None, + password=None, *args, **kwargs): + """ + Connect to a CouchDB instance. + + @param host: A hostname string for the CouchDB server. + @param port: The port of the CouchDB server, as an integer. + @param dbName: (optional) The default database to connect to. + @param username: (optional) The username for authorization. + @param password: (optional) The password for authorization. + @returns: A :class:`twisted.internet.defer.Deferred` representing the + the client connection to the CouchDB instance. + """ + super(client.CouchDB, self).__init__(host, port, + dbName=dbName, + username=username, + password=password, + *args, **kwargs) + if dbName: + self.bindToDB(dbName) + else: + databases = self.listDB() + log.msg("Available databases: %s" % databases) + + def queryByEmailOrAlias(self, alias, dbDoc="User", + view="by_email_or_alias"): + """ + Check to see if a particular email or alias exists. + + @param alias: A string representing the email or alias to check. + @param dbDoc: The CouchDB document to open. + @param view: The view of the CouchDB document to use. + """ + assert isinstance(alias, str), "Email or alias queries must be string" + + ## Prepend a forward slash, in case we forgot it: + if not alias.startswith('/'): + alias = '/' + alias + + d = self.openDoc(dbDoc) + d.addCallbacks(self.openView, log.err, (view)) + d.addCallbacks(self.get, log.err, (alias)) + d.addCallbacks(self.parseResult, log.err) + + @d.addCallback + def show_answer(result): + log.msg("Query: %s" % alias) + log.msg("Answer: %s" % alias) + + return d + + def query(self, uri): + """ + Query a CouchDB instance that we are connected to. + """ + try: + self.checkURI(uri) ## xxx write checkURI() + ## xxx we might be able to use self._parseURI() + except SchemeNotSupported, sns: ## xxx where in paisley is this? + log.exception(sns) ## xxx need log.exception() + + d = self.get(uri) + @d.addCallback + def parse_answer(answer): + return answer + + return answer + + @defer.inlineCallbacks + def listUsersAndEmails(self, limit=1000, reverse=False): + """ + List all users and email addresses, up to the given limit. + """ + query = "/users/_design/User/_view/by_email_or_alias/?reduce=false" + answer = yield self.query(query, limit=limit, reverse=reverse) + + if answer: + parsed = yield self.parseResult(answer) + if parsed: + log.msg("%s" % parsed) + else: + log.msg("No answer from database, perhaps there are no users.") + else: + log.msg("Problem querying CouchDB instance...") -- cgit v1.2.3