How do you map an email address onto a SOA RNAME field? - python

Is there an existing/standard algorithm for mapping an email address onto a RNAME field of a SOA record (and its inverse)? I'm using the dnspython package but I don't see anything in their source tree to handle this. I ran into the edge case of having a period '.' in the username that needs to be escaped and wondering if there are any other edge cases that I am missing. RFC 1035 simply states:
A <domain-name> which specifies the mailbox of the person responsible for this zone.
None of the RFCs that update 1035 expand upon RNAME field aside from a brief mention in RFC 1183.

Here is what I came up with using dnspython:
from dns.name import from_text
def email_to_rname(email):
"""Convert standard email address into RNAME field for SOA record.
>>> email_to_rname('johndoe#example.com')
<DNS name johndoe.example.com.>
>>> email_to_rname('john.doe#example.com')
<DNS name john\.doe.example.com.>
>>> print email_to_rname('johndoe#example.com')
johndoe.example.com.
>>> print email_to_rname('john.doe#example.com')
john\.doe.example.com.
"""
username, domain = email.split('#', 1)
username = username.replace('.', '\\.') # escape . in username
return from_text('.'.join((username, domain)))
def rname_to_email(rname):
"""Convert SOA record RNAME field into standard email address.
>>> rname_to_email(from_text('johndoe.example.com.'))
'johndoe#example.com'
>>> rname_to_email(from_text('john\\.doe.example.com.'))
'john.doe#example.com'
>>> rname_to_email(email_to_rname('johndoe#example.com'))
'johndoe#example.com'
>>> rname_to_email(email_to_rname('john.doe#example.com'))
'john.doe#example.com'
"""
labels = list(rname)
username, domain = labels[0], '.'.join(labels[1:]).rstrip('.')
username = username.replace('\\.', '.') # unescape . in username
return '#'.join((username, domain))

Related

Faster search of email in GAL using Python

I need to create a Python script which grabs different information about 1500 Outlook Contacts (out of 20000), based on their email. Until now, I managed to do this:
def grab_user_details(email):
first_name, last_name, department, location = '', '', '', ''
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
gal = outlook.Session.GetGlobalAddressList()
entries = gal.AddressEntries
for i in entries:
user = i.GetExchangeUser()
if user is not None:
if user.PrimarySmtpAddress == email:
first_name = email.split("#")[0].split(".")[0].title()
last_name = email.split("#")[0].split(".")[1].title()
department = user.Department
location = user.OfficeLocation
print(email, first_name, last_name, department, location)
break
In the end, the code just iterates through the GAL for that specific email. After finding it, it breaks, and continues searching for the next email. This method is fast for emails starting with A, or at least B... but when you have a GAL with 20000 emails, you can't just wait 2 days for it to finish the whole alphabet.
Is there a faster way of doing this?
Thanks!
Use the CreateRecipient method of the Namespace class to get an instance of the Recipient class based on the email address.
Sub TestRecipient()
Dim myNamespace As Outlook.NameSpace
Dim myRecipient As Outlook.Recipient
Set myNamespace = Application.GetNamespace("MAPI")
Set myRecipient = myNamespace.CreateRecipient("address#domain.com")
myRecipient.Resolve
If myRecipient.Resolved Then
' do some stuff here
End If
End Sub
The Recipient.AddressEntry property returns the AddressEntry object corresponding to the resolved recipient. Accessing the AddressEntry property forces resolution of an unresolved recipient name. If the name cannot be resolved, an error is returned. If the recipient is resolved, the Resolved property is True.
Then you can use the AddressEntry.GetExchangeUser method which returns an ExchangeUser object that represents the AddressEntry if the AddressEntry belongs to an Exchange AddressList object such as the Global Address List (GAL) and corresponds to an Exchange user.
Based on #Eugene Astafiev's answer (thank you!), I came up with the following code:
def grab_user_details(email):
name, department, location = '', '', ''
outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
user = outlook.Session.CreateRecipient(email)
user.Resolve()
print(user.Resolved)
try:
name = user.AddressEntry.GetExchangeUser().Name
department = user.AddressEntry.GetExchangeUser().Department
location = user.AddressEntry.GetExchangeUser().OfficeLocation
print(email, name, department, location)
except:
print("user NA")
This method is way faster than searching through the GAL.
There's one more thing I need to fix: unfortunately, some users have 2 email address', and .CreateRecipient(email) returns nothing, although outlook can find it. I need to dig into this a little bit more.

How can I search the Outlook (2010) Global Address List for multiple names?

I've read this post How can I search the Outlook (2010) Global Address List for a name? and found a working solution for getting a name from Outlook GAL.
I have 3 questions:
I can get the contact if the search_string is an email address. When it's a name, the search doesn't work. It would return False for resolved, but True for sendable. Then I get error when using ae object. What am I doing wrong?
I don't understand the code enough to modify it for searching multiple names. I simply created a for loop but maybe there is a more efficient way? For example, can I reuse the outlook.Session object between different searches?
Is the line recipient.Resolve() necessary?
Thanks in advance!
My attempt is below.
from __future__ import print_function
import win32com.client
search_strings = ['Doe John', 'Doe Jane']
outlook = win32com.client.gencache.EnsureDispatch('Outlook.Application')
for search_string in search_strings:
recipient = outlook.Session.CreateRecipient(search_string)
recipient.Resolve()
print('Resolved OK: ', recipient.Resolved)
print('Is it a sendable? (address): ', recipient.Sendable)
print('Name: ', recipient.Name)
ae = recipient.AddressEntry
email_address = None
if 'EX' == ae.Type:
eu = ae.GetExchangeUser()
email_address = eu.PrimarySmtpAddress
if 'SMTP' == ae.Type:
email_address = ae.Address
print('Email address: ', email_address)
Can't believe I found the solution so quickly after posting the question. Since it's hard to find the answer. I'm sharing my findings here.
It's inspired by How to fetch exact match of addressEntry object from GAL (Global Address List), though it's in c# rather than python.
This method uses exact match of displayname rather than relying on outlook to resolve the name. Though, it's possible to loop through the global address list and do partial match yourself.
import win32com.client
search_string = 'Doe John'
outlook = win32com.client.gencache.EnsureDispatch('Outlook.Application')
gal = outlook.Session.GetGlobalAddressList()
entries = gal.AddressEntries
ae = entries[search_string]
email_address = None
if 'EX' == ae.Type:
eu = ae.GetExchangeUser()
email_address = eu.PrimarySmtpAddress
if 'SMTP' == ae.Type:
email_address = ae.Address
print('Email address: ', email_address)

Python Retrieve Email Addresses

I want to create a Python program to resend a email using "email" and "smtplib" package.
import email
import smtplib
f = open('email_source.eml')
em = email.message_from_file(f)
email_from = em['FROM'] # '"Me" <me#xyz.com>'
email_to = em['TO'] # '"John, A" <john#abc.com>, "Peter, B" <peter#def.com>'
In above case, I have 2 recipients, I want to resend to these 2 person by smtplib.
import smtplib
smtp = smtplib.SMTP('localhost', '25')
smtp.sendmail(email_from, email_to, em.as_string())
If I put the string email_to into sendmail, it only send the email to first people. If I replace the email_to by a list,
email_to_list = ['"John, A" <jphn#abc.com>', '"Peter, B" <peter#def.com>']
the email can sent to both person.
My problem is, can I extract the recipients to a list from the em['TO'] and em['CC'] string?
Thank you.
The problem is that smtp.sendmail requires a list of addresses, according to the documentation:
SMTP.sendmail(from_addr, to_addrs, msg, mail_options=[], rcpt_options=[])
Send mail. The required arguments are an RFC 822 from-address string, a list of RFC 822 to-address strings (a bare string will be treated as a list with 1 address) […]
From the email-package you get a string, which the smtp-package then interprets as only one address.
In simple words, you need to split your to-address-string into a list of addresses.
How do you do this? You could do it manually, but it's best to just rely on the library:
import email.utils
email_to_raw = '"John, A" <john#abc.com>, "Peter, B" <peter#def.com>'
# split into (Name, Addr) tuple
email_to_split = email.utils.getaddresses([email_to_raw])
# combine the tuples into addresses, but keep the list
email_to = [email.utils.formataddr(pair) for pair in email_to_split]
print(email_to) # ['"John, A" <john#abc.com>', '"Peter, B" <peter#def.com>']
After swearing a bit at the designer of the API, you wrap it up into a function:
import email.utils
def split_combined_addresses(addresses):
parts = email.utils.getaddresses(addresses)
return [email.utils.formataddr(name_addr) for name_addr in parts]
print(split_combined_addresses(email_to))
This is how I do.
I assume emails are separated with semi-colon,but you can replace ; to ,
Here is sample data
addresses='"Johnny Test" <johnny#test.com>; Jack <another#test.com>; "Scott Summers" <scotts#test.com>; noname#test.com'
Source code:
import email.utils
import re
def split_combined_addresses(addresses):
#remove special chars
addresses = re.sub("\n|\r|\t", "", addresses)
# addrs = re.findall(r'(.*?)\s<(.*?)>,?', addresses) #colon separated
addrs = re.findall(r'(.*?)\s<(.*?)>;?', addresses) #semicolon separated
# remove leading space in name .strip()
# remove double-quote in name
addrs_clean = [(i.replace('"','').strip(), j) for i,j in addrs]
# add missing emails without name
emails = re.findall(r"[\w.+-]+#[\w-]+\.[\w.-]+", addresses)
for email in emails:
if (not email in list(zip(*addrs_clean))[1]):
addrs_clean.append(('', email))
return addrs_clean

How to forward an email message captured using poplib to a different email address?

I have following script which processes emails and save them to csv file. there will be advancement to script where I will use mechanize lib to process the extracted emails data for further processing on an another web interface. There are times it may fail now I can trap that specific email without having any problem but how can I forward the trapped email to a different address where I can process it manually or see what's wrong with it?
Here's the script
import ConfigParser
import poplib
import email
import BeautifulSoup
import csv
import time
DEBUG = False
CFG = 'email' # 'email' or 'test_email'
#def get_config():
def get_config(fnames=['cron/orderP/get_orders.ini'], section=CFG):
"""
Read settings from one or more .ini files
"""
cfg = ConfigParser.SafeConfigParser()
cfg.read(*fnames)
return {
'host': cfg.get(section, 'host'),
'use_ssl': cfg.getboolean(section, 'use_ssl'),
'user': cfg.get(section, 'user'),
'pwd': cfg.get(section, 'pwd')
}
def get_emails(cfg, debuglevel=0):
"""
Returns a list of emails
"""
# pick the appropriate POP3 class (uses SSL or not)
#pop = [poplib.POP3, poplib.POP3_SSL][cfg['use_ssl']]
emails = []
try:
# connect!
print('Connecting...')
host = cfg['host']
mail = poplib.POP3(host)
mail.set_debuglevel(debuglevel) # 0 (none), 1 (summary), 2 (verbose)
mail.user(cfg['user'])
mail.pass_(cfg['pwd'])
# how many messages?
num_messages = mail.stat()[0]
print('{0} new messages'.format(num_messages))
# get text of messages
if num_messages:
get = lambda i: mail.retr(i)[1] # retrieve each line in the email
txt = lambda ss: '\n'.join(ss) # join them into a single string
eml = lambda s: email.message_from_string(s) # parse the string as an email
print('Getting emails...')
emails = [eml(txt(get(i))) for i in xrange(1, num_messages+1)]
print('Done!')
except poplib.error_proto, e:
print('Email error: {0}'.format(e.message))
mail.quit() # close connection
return emails
def parse_order_page(html):
"""
Accept an HTML order form
Returns (sku, shipto, [items])
"""
bs = BeautifulSoup.BeautifulSoup(html) # parse html
# sku is in first <p>, shipto is in second <p>...
ps = bs.findAll('p') # find all paragraphs in data
sku = ps[0].contents[1].strip() # sku as unicode string
shipto_lines = [line.strip() for line in ps[1].contents[2::2]]
shipto = '\n'.join(shipto_lines) # shipping address as unicode string
# items are in three-column table
cells = bs.findAll('td') # find all table cells
txt = [cell.contents[0] for cell in cells] # get cell contents
items = zip(txt[0::3], txt[1::3], txt[2::3]) # group by threes - code, description, and quantity for each item
return sku, shipto, items
def get_orders(emails):
"""
Accepts a list of order emails
Returns order details as list of (sku, shipto, [items])
"""
orders = []
for i,eml in enumerate(emails, 1):
pl = eml.get_payload()
if isinstance(pl, list):
sku, shipto, items = parse_order_page(pl[1].get_payload())
orders.append([sku, shipto, items])
else:
print("Email #{0}: unrecognized format".format(i))
return orders
def write_to_csv(orders, fname):
"""
Accepts a list of orders
Write to csv file, one line per item ordered
"""
outf = open(fname, 'wb')
outcsv = csv.writer(outf)
for poNumber, shipto, items in orders:
outcsv.writerow([]) # leave blank row between orders
for code, description, qty in items:
outcsv.writerow([poNumber, shipto, code, description, qty])
# The point where mechanize will come to play
def main():
cfg = get_config()
emails = get_emails(cfg)
orders = get_orders(emails)
write_to_csv(orders, 'cron/orderP/{0}.csv'.format(int(time.time())))
if __name__=="__main__":
main()
As we all know that POP3 is used solely for retrieval (those who know or have idea how emails work) so there is no point using POP3 for the sake of message sending that why I mentioned How to forward an email message captured with poplib to a different email address? as an question.
The complete answer was
smtplib can be used for that sake to forward an poplib captured email message, all you need to do is to capture the message body and send it using smtplib to the desired email address. Furthermore as Aleksandr Dezhin quoted I will agree with him as some SMTP servers impose different restrictions on message they are processed.
Beside that you can use sendmail to achieve that if you are on Unix machine.

How do I catch "split" exceptions in python?

I am trying to parse a list of email addresses to remove the username and '#' symbol only leaving the domain name.
Example: blahblah#gmail.com
Desired output: gmail.com
I have accomplished this with the following code:
for row in cr:
emailaddy = row[0]
(emailuser, domain) = row[0].split('#')
print domain
but my issue is when I encounter a improperly formatted email address. For example if the row contains "aaaaaaaaa" (instead of a valid email address) the program crashes with the error
(emailuser, domain) = row[0].split('#')
ValueError: need more than 1 value to unpack.
(as you would expect) Rather than check all the email addresses for their validity, I would rather just not update grab the domain and move on to the next record. How can I properly handle this error and just move on?
So for the list of:
blahblah#gmail.com
mmymymy#hotmail.com
youououou
nonononon#yahoo.com
I would like the output to be:
gmail.com
hotmail.com
yahoo.com
Thanks!
You want something like this?
try:
(emailuser, domain) = row[0].split('#')
except ValueError:
continue
You can just filter out the address which does not contain #.
>>> [mail.split('#')[1] for mail in mylist if '#' in mail]
['gmail.com', 'hotmail.com', 'yahoo.com']
>>>
What about
splitaddr = row[0].split('#')
if len(splitaddr) == 2:
domain = splitaddr[1]
else:
domain = ''
This even handles cases like aaa#bbb#ccc and makes it invalid ('').
Try this
In [28]: b = ['blahblah#gmail.com',
'mmymymy#hotmail.com',
'youououou',
'nonononon#yahoo.com']
In [29]: [x.split('#')[1] for x in b if '#' in x]
Out[29]: ['gmail.com', 'hotmail.com', 'yahoo.com']
This does what you want:
import re
l=["blahblah#gmail.com","mmymymy#hotmail.com",
"youououou","nonononon#yahoo.com","amy#bong#youso.com"]
for e in l:
if '#' in e:
l2=e.split('#')
print l2[-1]
else:
print
Output:
gmail.com
hotmail.com
yahoo.com
youso.com
It handles the case where an email might have more than one '#' and just takes the RH of that.
if '#' in row[0]:
user, domain = row[0].split('#')
print domain
Maybe the best solution is to avoid exception handling all together.
You can do this by using the builtin function partition(). It is similar to split() but does not raise ValueError when the seperator is not found. Read more: https://docs.python.org/3/library/stdtypes.html#str.partition
We can consider the string not having '#' symbol, as a simple username:
try:
(emailuser, domain) = row[0].split('#')
print "Email User" + emailuser
print "Email Domain" + domain
except ValueError:
emailuser = row[0]
print "Email User Only" + emailuser
O/P:
Email User : abc
Email Domain : gmail.com
Email User : xyz
Email Domain : gmail.com
Email User Only : usernameonly

Categories