class StateBasedChainer(MultiTargetChainer): """<class maturity="stable" abstract="yes"> <summary> Class encapsulating connection establishment with multiple target addresses and keeping down state between connects. </summary> <description> <para> This class encapsulates a real TCP/IP connection establishment, and is used when a top-level proxy wants to perform chaining. In addition to ConnectChainer, this class adds the capability to perform stateful, load balance server connections among a set of IP addresses. </para> <note> <para>Both the <link linkend="python.Chainer.FailoverChainer">FailoverChainer</link> and <link linkend="python.Chainer.RoundRobinChainer">RoundRobinChainer</link> classes are derived from StateBasedChainer.</para> </note> </description> <metainfo> <attributes> <attribute internal="yes"> <name>state</name> <type></type> <description>Down state of target hosts. </description> </attribute> </attributes> </metainfo> </class> """ def __init__(self, protocol=ZD_PROTO_AUTO, timeout_connect=None, timeout_state=None): """<method maturity="stable"> <summary> Constructor to initialize a StateBasedChainer instance. </summary> <description> <para> This constructor initializes a StateBasedChainer class by filling arguments with appropriate values and calling the inherited constructor. </para> </description> <metainfo> <arguments> <argument maturity="stable"> <name>protocol</name> <type> <link id="zorp.proto.id"/> </type> <default>ZD_PROTO_AUTO</default> <description> Optional, specifies connection protocol (<parameter> ZD_PROTO_TCP</parameter> or <parameter>ZD_PROTO_UDP </parameter>), when not specified it defaults to the same protocol used on the client side. </description> </argument> <argument> <name>timeout_connect</name> <type> <integer/> </type> <default>30000</default> <description> Specifies connection timeout to be used when connecting to the target server. </description> </argument> <argument> <name>timeout_state</name> <type> <integer/> </type> <default>60000</default> <description> The down state of remote hosts is kept for this interval in miliseconds. </description> </argument> </arguments> </metainfo> </method> """ MultiTargetChainer.__init__(self, protocol, timeout_connect) if not timeout_state: timeout_state = 60000 self.state = TimedCache('chainer-state', int((timeout_state + 999) / 1000), update_stamp=FALSE) def getNextTarget(self, session): """<method internal="yes"> </method> """ while 1: (target_local, target_remote) = MultiTargetChainer.getNextTarget(self, session) if not target_remote: # we enumerated all targets try: session.chainer_targets_enumerated = session.chainer_targets_enumerated + 1 except AttributeError: session.chainer_targets_enumerated = 1 if not self.state or session.chainer_targets_enumerated == 2: # we enumerated all our targets twice, once # with state held, and then all state # cleared, we were not successful, terminate # target iteration log( None, CORE_MESSAGE, 4, "All destinations are down for two full iterations, giving up;" ) return (None, None) ## LOG ## # This message reports that the remote end is down and Zorp stores the # down state of the remote end, so Zorp wont try to connect to it within the # timeout latter. ## log( None, CORE_MESSAGE, 4, "All destinations are down, clearing cache and trying again;" ) # we enumerated all targets, and all of them were # down, clear our state and try once more self.state.clear() self.restart(session) continue is_host_down = self.state.lookup(target_remote.ip_s) if not is_host_down: return (target_local, target_remote) else: ## LOG ## # This message reports that the remote end is down, but Zorp does not store the # down state of the remote end, so Zorp will try to connect to it next time. ## log(session.session_id, CORE_MESSAGE, 4, "Destination is down, skipping; remote='%s'", (target_remote, )) def disableTarget(self, session, target_local, target_remote): """<method internal="yes"> </method> """ ## LOG ## # This message reports that the remote end is down and Zorp stores the # down state of the remote end, so Zorp wont try to connect to it within the # timeout latter. ## log(session.session_id, CORE_MESSAGE, 4, "Destination is down, keeping state; remote='%s'", (target_remote, )) self.state.store(target_remote.ip_s, 1)
class SmtpInvalidRecipientMatcher(AbstractMatcher): """<class maturity="stable" type="matcher"> <summary> Class verifying the validity of the recipient addresses in E-mails. </summary> <description> <para> This class encapsulates a VRFY/RCPT based validity checker to transparently verify the existance of E-mail addresses. Instead of immediately sending the e-mail to the recipient SMTP server, Zorp queuries an independent SMTP server about the existance of the recipient e-mail address. </para> <para> Instances of this class can be referred to in the <parameter>recipient_matcher</parameter> attribute of the <link linkend="python.Smtp.SmtpProxy">SmtpProxy</link> class. The SmtpProxy will automatically reject unknown recipients even if the recipient SMTP server would accept them. </para> <example> <title>SmtpInvalidMatcher example</title> <synopsis>Python: class SmtpRecipientMatcherProxy(SmtpProxy): recipient_matcher="SmtpCheckrecipient" def config(self): super(SmtpRecipientMatcherProxy, self).config() MatcherPolicy(name="SmtpCheckrecipient", matcher=SmtpInvalidRecipientMatcher (server_port=25, cache_timeout=60, attempt_delivery=FALSE, force_delivery_attempt=FALSE, server_name="recipientcheck.example.com"))</synopsis> </example> </description> <metainfo> <attributes/> </metainfo> </class> """ def __init__(self, server_name, server_port=25, cache_timeout=60, attempt_delivery=FALSE, force_delivery_attempt=FALSE, sender_address='<>', bind_name=''): """<method maturity="stable"> <summary> </summary> <description> </description> <metainfo> <arguments> <argument maturity="stable"> <name>server_name</name> <type> <string/> </type> <description> Domain name of the SMTP server that will verify the addresses. </description> </argument> <argument maturity="stable"> <name>server_port</name> <type> <integer/> </type> <default>25</default> <description> Port of the target server. </description> </argument> <argument maturity="stable"> <name>cache_timeout</name> <type> <integer/> </type> <default>60</default> <description> How long will the result of an address verification be retained (in seconds). </description> </argument> <argument maturity="obsolete"> <name>attempt_delivery</name> <type> <boolean/> </type> <default>FALSE</default> <description> Obsolete, ignored. </description> </argument> <argument maturity="stable"> <name>force_delivery_attempt</name> <type> <boolean/> </type> <default>FALSE</default> <description> Force a delivery attempt even if the autodetection code otherwise would use VRFY. Useful if the server always returns success for VRFY. </description> </argument> <argument maturity="stable"> <name>sender_address</name> <type> <string/> </type> <default>"<>"</default> <description> This value will be used as the mail sender for the attempted mail delivery. Mail delivery is attempted if the <parameter>force_delivery_attempt</parameter> is TRUE, or the recipient server does not support the VRFY command. </description> </argument> <argument maturity="stable"> <name>bind_name</name> <type> <string/> </type> <default>""</default> <description> Specifies the hostname to bind to before initiating the connection to the SMTP server. </description> </argument> </arguments> </metainfo> </method> """ super(SmtpInvalidRecipientMatcher, self).__init__() self.force_delivery_attempt = force_delivery_attempt self.server_name = server_name self.server_port = server_port self.bind_name = bind_name self.sender_address = sender_address self.cache = TimedCache('smtp_valid_recipients(%s)' % server_name, cache_timeout) def checkMatch(self, email): """<method internal="yes"> </method> """ # email is a fully qualified email address like [email protected] try: cached = self.cache.lookup(email) if cached != None: ## LOG ## # This message reports that the recipient address has been already checked and # Zorp uses the cached information. ## log(None, CORE_DEBUG, 6, "Cached recipient match found; email='%s', cached='%d'", (email, cached)) if cached: return TRUE else: return FALSE except KeyError: cached = None try: ## LOG ## # This message reports that the recipient address has not been already checked and # Zorp is going to check it now directly. ## log(None, CORE_DEBUG, 6, "Recipient validity not cached, trying the direct way; email='%s'", (email)) server = SmtpProto(self.server_name, self.server_port, bind_addr=self.bind_name) try: (smtp_code, smtp_msg) = server.ehlo() if smtp_code > 299: (smtp_code, smtp_msg) = server.helo() esmtp = FALSE else: esmtp = TRUE if smtp_code > 299: raise MatcherException, "Server refused our EHLO/HELO command." present = FALSE smtp_code = -1 if not self.force_delivery_attempt and (not esmtp or server.has_extn("VRFY")): log(None, CORE_DEBUG, 6, "Trying to use VRFY to check email address validity; email='%s'", (email,)) (smtp_code, smtp_msg) = server.verify(email) present = (smtp_code < 300) else: log(None, CORE_DEBUG, 6, "Attempting delivery to check email address validity; email='%s'", (email,)) (smtp_code, smtp_msg) = server.mail(self.sender_address) if smtp_code == 250: (smtp_code, smtp_msg) = server.rcpt(email) present = (smtp_code < 300) else: ## LOG ## # This message indicates that the sender address was rejected during the recipient address # verify check and Zorp rejects the recipient address. ## log(None, CORE_ERROR, 3, "SMTP sender was rejected, unable to verify user existence; email='%s', server_address='%s', server_port='%d'", (email, self.server_name, self.server_port)) raise MatcherException, "Server has not accepted our sender (%s)." % self.sender_address if present: ## LOG ## # This message reports that the recipient address verify was successful and Zorp accepts it. ## log(None, CORE_INFO, 5, "Server accepted recipient; email='%s'", email) # we only cache successful lookups self.cache.store(email, not present) elif smtp_code != -1: ## LOG ## # This message reports that the recipient address verify was unsuccessful and Zorp rejects it. ## log(None, CORE_INFO, 4, "Server rejected recipient; email='%s'", email) finally: server.quit() except (socket.error, smtplib.SMTPException), e: ## LOG ## # This message indicates that an SMTP error occurred during the recipient address verify # and Zorp rejects it. ## log(None, CORE_ERROR, 3, "SMTP error during recipient validity checking; info='%s'", e) raise MatcherException, "SMTP error or socket failure while checking user validity (%s)" % str(e) # we return when we want to reject... return not present
class StateBasedChainer(MultiTargetChainer): """<class maturity="stable" abstract="yes"> <summary> Class encapsulating connection establishment with multiple target addresses and keeping down state between connects. </summary> <description> <para> This class encapsulates a real TCP/IP connection establishment, and is used when a top-level proxy wants to perform chaining. In addition to ConnectChainer, this class adds the capability to perform stateful, load balance server connections among a set of IP addresses. </para> <note> <para>Both the <link linkend="python.Chainer.FailoverChainer">FailoverChainer</link> and <link linkend="python.Chainer.RoundRobinChainer">RoundRobinChainer</link> classes are derived from StateBasedChainer.</para> </note> </description> <metainfo> <attributes> <attribute internal="yes"> <name>state</name> <type></type> <description>Down state of target hosts. </description> </attribute> </attributes> </metainfo> </class> """ def __init__(self, protocol=ZD_PROTO_AUTO, timeout_connect=None, timeout_state=None): """<method maturity="stable"> <summary> Constructor to initialize a StateBasedChainer instance. </summary> <description> <para> This constructor initializes a StateBasedChainer class by filling arguments with appropriate values and calling the inherited constructor. </para> </description> <metainfo> <arguments> <argument maturity="stable"> <name>protocol</name> <type> <link id="zorp.proto.id"/> </type> <default>ZD_PROTO_AUTO</default> <description> Optional, specifies connection protocol (<parameter> ZD_PROTO_TCP</parameter> or <parameter>ZD_PROTO_UDP </parameter>), when not specified it defaults to the same protocol used on the client side. </description> </argument> <argument> <name>timeout_connect</name> <type> <integer/> </type> <default>30000</default> <description> Specifies connection timeout to be used when connecting to the target server. </description> </argument> <argument> <name>timeout_state</name> <type> <integer/> </type> <default>60000</default> <description> The down state of remote hosts is kept for this interval in miliseconds. </description> </argument> </arguments> </metainfo> </method> """ MultiTargetChainer.__init__(self, protocol, timeout_connect) if not timeout_state: timeout_state = 60000 self.state = TimedCache('chainer-state', int((timeout_state + 999) / 1000), update_stamp=FALSE) def getNextTarget(self, session): """<method internal="yes"> </method> """ while 1: (target_local, target_remote) = MultiTargetChainer.getNextTarget(self, session) if not target_remote: # we enumerated all targets try: session.chainer_targets_enumerated = session.chainer_targets_enumerated + 1 except AttributeError: session.chainer_targets_enumerated = 1 if not self.state or session.chainer_targets_enumerated == 2: # we enumerated all our targets twice, once # with state held, and then all state # cleared, we were not successful, terminate # target iteration log(None, CORE_MESSAGE, 4, "All destinations are down for two full iterations, giving up;") return (None, None) ## LOG ## # This message reports that the remote end is down and Zorp stores the # down state of the remote end, so Zorp wont try to connect to it within the # timeout latter. ## log(None, CORE_MESSAGE, 4, "All destinations are down, clearing cache and trying again;") # we enumerated all targets, and all of them were # down, clear our state and try once more self.state.clear() self.restart(session) continue is_host_down = self.state.lookup(target_remote.ip_s) if not is_host_down: return (target_local, target_remote) else: ## LOG ## # This message reports that the remote end is down, but Zorp does not store the # down state of the remote end, so Zorp will try to connect to it next time. ## log(session.session_id, CORE_MESSAGE, 4, "Destination is down, skipping; remote='%s'", (target_remote,)) def disableTarget(self, session, target_local, target_remote): """<method internal="yes"> </method> """ ## LOG ## # This message reports that the remote end is down and Zorp stores the # down state of the remote end, so Zorp wont try to connect to it within the # timeout latter. ## log(session.session_id, CORE_MESSAGE, 4, "Destination is down, keeping state; remote='%s'", (target_remote,)) self.state.store(target_remote.ip_s, 1)