def _get_sender(self, mlist, msg, msgdata): """Return the recipient's address VERP encoded in the sender. :param mlist: The mailing list being delivered to. :type mlist: `IMailingList` :param msg: The original message being delivered. :type msg: `Message` :param msgdata: Additional message metadata for this delivery. :type msgdata: dictionary """ sender = super(VERPMixin, self)._get_sender(mlist, msg, msgdata) if msgdata.get("verp", False): log.debug("VERPing %s", msg.get("message-id")) recipient = msgdata["recipient"] sender_mailbox, sender_domain = split_email(sender) # Encode the recipient's address for VERP. recipient_mailbox, recipient_domain = split_email(recipient) if recipient_domain is None: # The recipient address is not fully-qualified. We can't # deliver it to this person, nor can we craft a valid verp # header. I don't think there's much we can do except ignore # this recipient. log.info("Skipping VERP delivery to unqual recip: %s", recipient) return sender return "{0}@{1}".format( expand( config.mta.verp_format, dict(bounces=sender_mailbox, local=recipient_mailbox, domain=DOT.join(recipient_domain)), ), DOT.join(sender_domain), ) else: return sender
def _get_sender(self, mlist, msg, msgdata): """Return the recipient's address VERP encoded in the sender. :param mlist: The mailing list being delivered to. :type mlist: `IMailingList` :param msg: The original message being delivered. :type msg: `Message` :param msgdata: Additional message metadata for this delivery. :type msgdata: dictionary """ sender = super()._get_sender(mlist, msg, msgdata) if msgdata.get('verp', False): log.debug('VERPing %s', msg.get('message-id')) recipient = msgdata['recipient'] sender_mailbox, sender_domain = split_email(sender) # Encode the recipient's address for VERP. recipient_mailbox, recipient_domain = split_email(recipient) if recipient_domain is None: # The recipient address is not fully-qualified. We can't # deliver it to this person, nor can we craft a valid verp # header. I don't think there's much we can do except ignore # this recipient. log.info('Skipping VERP delivery to unqual recip: %s', recipient) return sender return '{0}@{1}'.format( expand( config.mta.verp_format, mlist, dict(bounces=sender_mailbox, local=recipient_mailbox, domain=DOT.join(recipient_domain))), DOT.join(sender_domain)) else: return sender
def send_probe(member, msg): """Send a VERP probe to the member. :param member: The member to send the probe to. From this object, both the user and the mailing list can be determined. :type member: IMember :param msg: The bouncing message that caused the probe to be sent. :type msg: :return: The token representing this probe in the pendings database. :rtype: string """ mlist = getUtility(IListManager).get_by_list_id( member.mailing_list.list_id) text = make( 'probe.txt', mlist, member.preferred_language.code, listname=mlist.fqdn_listname, address=member.address.email, optionsurl=member.options_url, owneraddr=mlist.owner_address, ) message_id = msg['message-id'] if isinstance(message_id, bytes): message_id = message_id.decode('ascii') pendable = _ProbePendable( # We can only pend unicodes. member_id=member.member_id.hex, message_id=message_id, ) token = getUtility(IPendings).add(pendable) mailbox, domain_parts = split_email(mlist.bounces_address) probe_sender = Template(config.mta.verp_probe_format).safe_substitute( bounces=mailbox, token=token, domain=DOT.join(domain_parts), ) # Calculate the Subject header, in the member's preferred language. with _.using(member.preferred_language.code): subject = _('$mlist.display_name mailing list probe message') # Craft the probe message. This will be a multipart where the first part # is the probe text and the second part is the message that caused this # probe to be sent. probe = UserNotification(member.address.email, probe_sender, subject, lang=member.preferred_language) probe.set_type('multipart/mixed') notice = MIMEText(text, _charset=mlist.preferred_language.charset) probe.attach(notice) probe.attach(MIMEMessage(msg)) # Probes should not have the Precedence: bulk header. probe.send(mlist, envsender=probe_sender, verp=False, probe_token=token, add_precedence=False) return token
def is_valid(self, email): """See `IEmailValidator`.""" if not email or " " in email: return False if _badchars.search(email) or email[0] == "-": return False user, domain_parts = split_email(email) # Local, unqualified addresses are not allowed. if not domain_parts: return False if len(domain_parts) < 2: return False return True
def is_valid(self, email): """See `IEmailValidator`.""" if not email or ' ' in email: return False if _badchars.search(email) or email[0] == '-': return False user, domain_parts = split_email(email) # Local, unqualified addresses are not allowed. if not domain_parts: return False if len(domain_parts) < 2: return False return True
def send_probe(member, msg): """Send a VERP probe to the member. :param member: The member to send the probe to. From this object, both the user and the mailing list can be determined. :type member: IMember :param msg: The bouncing message that caused the probe to be sent. :type msg: :return: The token representing this probe in the pendings database. :rtype: string """ mlist = getUtility(IListManager).get_by_list_id( member.mailing_list.list_id) text = make('probe.txt', mlist, member.preferred_language.code, listname=mlist.fqdn_listname, address=member.address.email, optionsurl=member.options_url, owneraddr=mlist.owner_address, ) message_id = msg['message-id'] if isinstance(message_id, bytes): message_id = message_id.decode('ascii') pendable = _ProbePendable( # We can only pend unicodes. member_id=member.member_id.hex, message_id=message_id, ) token = getUtility(IPendings).add(pendable) mailbox, domain_parts = split_email(mlist.bounces_address) probe_sender = Template(config.mta.verp_probe_format).safe_substitute( bounces=mailbox, token=token, domain=DOT.join(domain_parts), ) # Calculate the Subject header, in the member's preferred language. with _.using(member.preferred_language.code): subject = _('$mlist.display_name mailing list probe message') # Craft the probe message. This will be a multipart where the first part # is the probe text and the second part is the message that caused this # probe to be sent. probe = UserNotification(member.address.email, probe_sender, subject, lang=member.preferred_language) probe.set_type('multipart/mixed') notice = MIMEText(text, _charset=mlist.preferred_language.charset) probe.attach(notice) probe.attach(MIMEMessage(msg)) # Probes should not have the Precedence: bulk header. probe.send(mlist, envsender=probe_sender, verp=False, probe_token=token, add_precedence=False) return token
def is_valid(self, email): """See `IEmailValidator`.""" if not email: return False user, domain_parts = split_email(email) if not user or len(_valid_local.sub('', user)) > 0: return False # Local, unqualified addresses are not allowed. if not domain_parts: return False if len(domain_parts) < 2: return False for p in domain_parts: if len(p) == 0 or p[0] == '-' or len(_valid_domain.sub('', p)) > 0: return False return True
def get_verp(self, mlist, msg): """Extract a set of VERP bounce addresses. :param mlist: The mailing list being checked. :type mlist: `IMailingList` :param msg: The message being parsed. :type msg: `email.message.Message` :return: The set of addresses extracted from the VERP headers. :rtype: set of strings """ blocal, bdomain = split_email(mlist.bounces_address) values = set() verp_matches = set() for header in ('to', 'delivered-to', 'envelope-to', 'apparently-to'): values.update(msg.get_all(header, [])) for field in values: address = parseaddr(field)[1] if not address: # This header was empty. continue mo = self._cre.search(address) if not mo: # This did not match the VERP regexp. continue try: if blocal != mo.group('bounces'): # This was not a bounce to our mailing list. continue original_address = self._get_address(mo) except IndexError: elog.error('Bad VERP pattern: {0}'.format(self._pattern)) return set() else: if original_address is not None: verp_matches.add(original_address) return verp_matches
def test_no_at_split(self): self.assertEqual(split_email('anne'), ('anne', None))
def test_normal_split(self): self.assertEqual(split_email('*****@*****.**'), ('anne', ['example', 'com'])) self.assertEqual(split_email('*****@*****.**'), ('anne', ['foo', 'example', 'com']))
def send_probe(member, msg=None, message_id=None): """Send a VERP probe to the member. :param member: The member to send the probe to. From this object, both the user and the mailing list can be determined. :type member: IMember :param msg: The bouncing message that caused the probe to be sent. :type msg: :param message_id: MessageID of the bouncing message. :type message_id: str :return: The token representing this probe in the pendings database. :rtype: string """ if (message_id or msg) is None: raise ValueError('Required at least one of "message_id" and "msg".') mlist = getUtility(IListManager).get_by_list_id( member.mailing_list.list_id) template = getUtility(ITemplateLoader).get( 'list:user:notice:probe', mlist, language=member.preferred_language.code, # For backward compatibility. code=member.preferred_language.code, ) text = wrap(expand(template, mlist, dict( sender_email=member.address.email, # For backward compatibility. address=member.address.email, email=member.address.email, owneraddr=mlist.owner_address, ))) if message_id is None: message_id = msg['message-id'] if isinstance(message_id, bytes): message_id = message_id.decode('ascii') pendable = _ProbePendable( # We can only pend unicodes. member_id=member.member_id.hex, message_id=message_id, ) token = getUtility(IPendings).add(pendable) mailbox, domain_parts = split_email(mlist.bounces_address) probe_sender = Template(config.mta.verp_probe_format).safe_substitute( bounces=mailbox, token=token, domain=DOT.join(domain_parts), ) # Calculate the Subject header, in the member's preferred language. with _.using(member.preferred_language.code): subject = _('$mlist.display_name mailing list probe message') # Craft the probe message. This will be a multipart where the first part # is the probe text and the second part is the message that caused this # probe to be sent, if it provied. probe = UserNotification(member.address.email, probe_sender, subject, lang=member.preferred_language) probe.set_type('multipart/mixed') notice = MIMEText(text, _charset=member.preferred_language.charset) probe.attach(notice) if msg is not None: probe.attach(MIMEMessage(msg)) # Probes should not have the Precedence: bulk header. probe.send(mlist, sender=probe_sender, verp=False, probe_token=token, add_precedence=False) # When we send a probe, we reset the score. member.bounce_score = 0 return token
def test_no_at_split(self): self.assertEqual(split_email("anne"), ("anne", None))
def test_normal_split(self): self.assertEqual(split_email("*****@*****.**"), ("anne", ["example", "com"])) self.assertEqual(split_email("*****@*****.**"), ("anne", ["foo", "example", "com"]))