Exemplo n.º 1
0
def handle_email(k):
	global address_list
	userdata = None

	## read email from file
	msg_raw = read_email(k)
	if not msg_raw:
		logging.error('Could not open email file: ' + k)
		return


	## extract header
	msg_headers = Parser().parsestr(msg_raw)

	## check if email was valid
	if not msg_headers:
		logging.error('Malformed email detected and purged')
		delete_email(k)
		return

	## find email source and dest addresses
	msg_sender    = msg_headers["From"]

	## failed delivery email
	if msg_sender == '<>' or not msg_sender:
		msg_sender = BMConfig().get("bmgateway", "bmgateway", "relay_address_label")
	else:
		try:
			msg_sender    = re.findall(r'[\w\.+-]+@[\w\.-]+.[\w]+', msg_sender)[0]
		except:
			pass
	msg_sender = msg_sender.lower()

	msg_recipient = ""

	## find email details
	if msg_headers["To"]:
		rcpts = re.findall(r'[\w\.+-]+@[\w\.-]+.[\w]+', msg_headers["To"])
		if len(rcpts) > 0:
			msg_recipient = rcpts[0]
			## strip extension (user+foo@domain)
			msg_recipient = re.sub(r'\+.*@', '@', msg_recipient) 
			msg_recipient = msg_recipient.lower()
			userdata = lib.user.GWUser(email = msg_recipient, unalias = True)

	## check if we have a recipient address for the receiving email
	if not userdata or not userdata.check():
		## look for X-Original-To instead
		rcpts = re.findall(r'[\w\.+-]+@[\w\.-]+.[\w]+', msg_headers["X-Original-To"])
		if len(rcpts) > 0:
			msg_recipient = rcpts[0]
			msg_recipient = re.sub(r'\+.*@', '@', msg_recipient) 
			msg_recipient = msg_recipient.lower()
			userdata = lib.user.GWUser(email = msg_recipient, unalias = True)

	## no valid recipient
	#if not msg_recipient in addressbook:
	#	logging.warn('Purged email destined for unknown user ' + msg_recipient + ' from ' + msg_sender)
	#	logging.debug(msg_headers)
	#	delete_email(k)
	#	return

	## check if we have valid sender and recipient details
	if not msg_sender or not msg_recipient:
		logging.warn('Malformed email detected and purged')
		delete_email(k)
		return

	## set bitmessage destination address
	bm_to_address = userdata.bm

	## set from address
	## check to see if we need to generate a sending address for the source email address
	# if not msg_sender in address_list:
	# 	bm_from_address = generate_sender_address(msg_sender)
	# 	address_list[msg_sender] = bm_from_address
	# else:
	bm_from_address = BMAPI().get_address(BMConfig().get("bmgateway", "bmgateway", "relay_address_label"))

	## find message subject
	msg_subject = decode_header(msg_headers['subject'])[0]
	if(msg_subject[1]):
		msg_subject = unicode(msg_subject[0], msg_subject[1])
	else:
		msg_subject = lib.charset.safeDecode(msg_subject[0])

	## find message body contents in plaintext
	msg_tmp = email.message_from_string(msg_raw)

	# handle DSN
	if msg_tmp.get_content_type() == "multipart/report" and msg_tmp.get_param("report-type", "") == "delivery-status" and msg_tmp.get("Auto-Submitted", "") == "auto-replied":
		for part in msg_tmp.walk():
			if part and part.get_content_type() == 'message/delivery-status':
				for subpart in part.get_payload(decode = 0):
					if subpart.get("Action", "") in ("relayed", "delivered", "expanded"):
						logging.info ("Successful DSN from " + bm_to_address)
						lib.user.GWUser(bm = bm_to_address).setlastrelay(lastrelay = time.time())
						delete_email(k)
						return

	msg_body = u''
	body_raw = ''
	decrypt_ok = False
	sigverify_ok = False
	mega_fileids = []

	# DKIM
	ar = msg_tmp.get_param("dkim", "missing", "Authentication-Results")
	if ar == "missing":
		try:
			domain = msg_sender.split("@")[-1]
			if lib.user.GWDomain(domain).check() and domain == msg_tmp.get_param("d", "missing", "DKIM-Signature"):
				ar = "pass" # we trust MTA to reject fakes
		except:
			pass

	## inline PGP
	for part in msg_tmp.walk():
		if part and part.get_content_type() == 'text/plain' and not (part.has_key("Content-Disposition") and part.__getitem__("Content-Disposition")[:11] == "attachment;"):
			part_str = part.get_payload(decode=1)
			if userdata.pgp == 1:
				if userdata.flags & 1 == 1:
					pgpparts = part_str.split("-----")
					# hack for absent pgp
					if not pgpparts or len(pgpparts) < 4:
						msg_body += lib.charset.safeDecode(part_str, part.get_content_charset(None))
						continue
					state = 0
					pgp_body = ""
					for pgppart in pgpparts:
						if pgppart == "BEGIN PGP MESSAGE":
							pgp_body = "-----" + pgppart + "-----"
							state = 1
						elif pgppart == "END PGP MESSAGE":
							pgp_body += "-----" + pgppart + "-----"
							# import from sql if necessary
							lib.gpg.check_key(msg_recipient)
							decrypted, sigverify_ok = lib.gpg.decrypt_content(pgp_body, msg_sender, msg_recipient)
							if isinstance(decrypted, basestring):
								part_str = decrypted
								decrypt_ok = True
							#else:
								#part_str = part.get_payload(decode = 0)
							sigresult = "fail"
							if sigverify_ok:
								sigresult = "ok"
							logging.info("Decrypted email from " + msg_sender + " to " + msg_recipient + ", signature: " + sigresult)
							state = 0
						elif pgppart == "BEGIN PGP SIGNED MESSAGE":
							pgp_body += "-----" + pgppart + "-----"
							state = 2
						elif pgppart == "BEGIN PGP SIGNATURE":
							pgp_body += "-----" + pgppart + "-----"
							state = 3
						elif pgppart == "END PGP SIGNATURE":
							pgp_body += "-----" + pgppart + "-----"
							# import from sql if necessary
							lib.gpg.check_key(msg_recipient)
							plain, sigverify_ok = lib.gpg.verify(pgp_body, msg_sender, msg_recipient)
							if isinstance(plain, basestring):
								part_str = plain
							#else:
								#part_str = part.get_payload(decode = 0)
							sigresult = "fail"
							if sigverify_ok:
								sigresult = "ok"
							logging.info("Verifying PGP signature from " + msg_sender + " to " + msg_recipient + ": " + sigresult)
							state = 0
						elif state == 0:
							msg_body += lib.charset.safeDecode(pgppart, part.get_content_charset(None))
						elif state > 0:
							pgp_body += lib.charset.safeDecode(pgppart, part.get_content_charset(None))
				else:
					if "BEGIN PGP MESSAGE" in part_str:
						# import from sql if necessary
						lib.gpg.check_key(msg_recipient)
						decrypted, sigverify_ok = lib.gpg.decrypt_content(part_str, msg_sender, msg_recipient)
						if isinstance(decrypted, basestring):
							part_str = decrypted
							decrypt_ok = True
						else:
							part_str = part.get_payload(decode = 0)
						logging.info("Decrypted email from " + msg_sender + " to " + msg_recipient)
					elif "BEGIN PGP SIGNED MESSAGE" in part_str:
						# import from sql if necessary
						lib.gpg.check_key(msg_recipient)
						plain, sigverify_ok = lib.gpg.verify(part_str, msg_sender, msg_recipient)
						if isinstance(plain, basestring):
							part_str = plain
						else:
							part_str = part.get_payload(decode = 0)
			# PGP END
			
			body_raw += part.as_string(False)
			#print part.get_content_charset()
			#print msg_tmp.get_charset()
			part_str = lib.charset.safeDecode(part_str, part.get_content_charset(None))
			msg_body += part_str
	
	## if there's no plaintext content, convert the html
	if not msg_body or userdata.html == 2:
		for part in msg_tmp.walk():
			if part and part.get_content_type() == 'text/html' and not (part.has_key("Content-Disposition") and part.__getitem__("Content-Disposition")[:11] == "attachment;"):
				part_str = part.get_payload(decode=1)
				h = html2text.HTML2Text()
				h.inline_links = False
				if userdata.html == 1:
					msg_body += lib.charset.safeDecode(part_str, part.get_content_charset(None))
				elif userdata.html == 2:
					msg_body = lib.charset.safeDecode(part_str, part.get_content_charset(None))
				else:
					msg_body += h.handle(lib.charset.safeDecode(part_str, part.get_content_charset(None)))
				#msg_body = msg_body + html2text.html2text(unicode(part.get_payload(), part.get_content_charset()))		
	
	## if there's no plaintext or html, check if it's encrypted
	# PGP/MIME
	has_encrypted_parts = False
	if not msg_body:
		for part in msg_tmp.walk():
			if part.get_content_type() == 'application/pgp-encrypted':
				has_encrypted_parts = True
				# import from sql if necessary
				if userdata.pgp == 1:
					lib.gpg.check_key(msg_recipient)

			## look for encrypted attachment containing text
			if part.get_content_type() == 'application/octet-stream' and has_encrypted_parts:
				part_str = part.get_payload(decode=1)

				if userdata.pgp == 0:
					msg_body += part_str
					continue

				## if we see the pgp header, decrypt
				if 'BEGIN PGP MESSAGE' in part_str:
					decrypted_data, sigverify_ok = lib.gpg.decrypt_content(part_str, msg_sender, msg_recipient, True)

					## decrypt failed
					if not decrypted_data:
						logging.error("Decryption of email destined for " + msg_recipient + " failed")
						msg_body += part.get_payload(decode=0)
						continue

					logging.info("Decrypted email from " + msg_sender + " to " + msg_recipient)
					msg_body += decrypted_data
					decrypt_ok = True
				elif "BEGIN PGP SIGNED MESSAGE" in part_str:
					plain, sigverify_ok = lib.gpg.verify(part_str, msg_sender, msg_recipient)
					if isinstance(plain, basestring):
						msg_body += plain
					else:
						msg_body += part.get_payload(decode = 0)
				
				## unknown attachment
				else:
					logging.debug("Received application/octet-stream type in inbound email, but did not see encryption header")

	if not sigverify_ok:
		for part in msg_tmp.walk():
			if part.get_content_type() == 'application/pgp-signature':

				if userdata.pgp == 0:
					msg_body = '-----BEGIN PGP SIGNED MESSAGE-----\n' + msg_body
					msg_body += '\n-----BEGIN PGP SIGNATURE-----\n'
					msg_body += part.get_payload(decode=0)
					msg_body += '\n-----END PGP SIGNATURE-----\n'
					continue

				# import from sql if necessary
				lib.gpg.check_key(msg_recipient)
				plain, sigverify_ok = lib.gpg.verify(body_raw, msg_sender, msg_recipient, part.get_payload(decode=1))

	if userdata.attachments == 1 and not userdata.expired():
		for part in msg_tmp.walk():
			if part.has_key("Content-Disposition") and part.__getitem__("Content-Disposition")[:11] == "attachment;":
				# fix encoding
				try:
					filename = email.header.decode_header(part.get_filename())
					encoding = filename[0][1]
					filename = filename[0][0]
				except:
					filename = part.get_filename()
					encoding = False

				fileid, link = lib.bmmega.mega_upload(userdata.bm, filename, part.get_payload(decode = 1))
				mega_fileids.append(fileid)
				if encoding:
					filename = unicode(filename, encoding)
				logging.info("Attachment \"%s\" (%s)", filename, part.get_content_type())
				msg_body = "Attachment \"" + filename + "\" (" + part.get_content_type() + "): " + link + "\n" + msg_body
	if userdata.pgp == 1:
		if not decrypt_ok:
			msg_body = "WARNING: PGP encryption missing or invalid. The message content could be exposed to third parties.\n" + msg_body
		if not sigverify_ok:
			msg_body = "WARNING: PGP signature missing or invalid. The authenticity of the message could not be verified.\n" + msg_body
	else:
		# msg_body = "WARNING: Server-side PGP is off, passing message as it is.\n" + msg_body
		pass
		
	if not ar[0:4] == "pass":
		msg_body = "WARNING: DKIM signature missing or invalid. The email may not have been sent through legitimate servers.\n" + msg_body

	logging.info('Incoming email from %s to %s', msg_sender, msg_recipient)

	sent = SendBM(bm_from_address, bm_to_address,
		'MAILCHUCK-FROM::' + msg_sender + ' | ' + msg_subject.encode('utf-8'),
		msg_body.encode('utf-8'))
	if sent.status:
		for fileid in mega_fileids:
			# cur.execute ("UPDATE mega SET ackdata = %s WHERE fileid = %s AND ackdata IS NULL", (ackdata.decode("hex"), fileid))
			pass
		## remove email file
		if userdata.archive == 1:
			#print msg_body
			save_email(k)
		else:
			delete_email(k)
def handle_email(k):
	global address_list
	userdata = None

	## read email from file
	msg_raw = read_email(k)
	if not msg_raw:
		logging.error('Could not open email file: ' + k)
		return


	## extract header
	msg_headers = Parser().parsestr(msg_raw)

	## check if email was valid
	if not msg_headers:
		logging.error('Malformed email detected and purged')
		delete_email(k)
		return

	## find email source and dest addresses
	msg_sender    = msg_headers["From"]

	## failed delivery email
	if msg_sender == '<>' or not msg_sender:
		msg_sender = BMConfig().get("bmgateway", "bmgateway", "relay_address_label")
	else:
		try:
			msg_sender    = re.findall(r'[\w\.+-]+@[\w\.-]+.[\w]+', msg_sender)[0]
		except:
			pass
	msg_sender = msg_sender.lower()

	msg_recipient = ""

	## find email details
	if msg_headers["To"]:
		rcpts = re.findall(r'[\w\.+-]+@[\w\.-]+.[\w]+', msg_headers["To"])
		if len(rcpts) > 0:
			msg_recipient = rcpts[0]
			## strip extension (user+foo@domain)
			msg_recipient = re.sub(r'\+.*@', '@', msg_recipient) 
			msg_recipient = msg_recipient.lower()
			userdata = lib.user.GWUser(email = msg_recipient, unalias = True)

	## check if we have a recipient address for the receiving email
	if not userdata.check():
		## look for X-Original-To instead
		rcpts = re.findall(r'[\w\.+-]+@[\w\.-]+.[\w]+', msg_headers["X-Original-To"])
		if len(rcpts) > 0:
			msg_recipient = rcpts[0]
			msg_recipient = re.sub(r'\+.*@', '@', msg_recipient) 
			msg_recipient = msg_recipient.lower()
			userdata = lib.user.GWUser(email = msg_recipient, unalias = True)

	## no valid recipient
	#if not msg_recipient in addressbook:
	#	logging.warn('Purged email destined for unknown user ' + msg_recipient + ' from ' + msg_sender)
	#	logging.debug(msg_headers)
	#	delete_email(k)
	#	return

	## check if we have valid sender and recipient details
	if not msg_sender or not msg_recipient:
		logging.warn('Malformed email detected and purged')
		delete_email(k)
		return

	## set bitmessage destination address
	bm_to_address = userdata.bm

	## set from address
	## check to see if we need to generate a sending address for the source email address
	# if not msg_sender in address_list:
	# 	bm_from_address = generate_sender_address(msg_sender)
	# 	address_list[msg_sender] = bm_from_address
	# else:
	bm_from_address = BMAPI().get_address(BMConfig().get("bmgateway", "bmgateway", "relay_address_label"))

	## find message subject
	msg_subject = decode_header(msg_headers['subject'])[0]
	if(msg_subject[1]):
		msg_subject = unicode(msg_subject[0], msg_subject[1])
	else:
		msg_subject = msg_subject[0]

	## find message body contents in plaintext
	msg_tmp = email.message_from_string(msg_raw)

	# handle DSN
	if msg_tmp.get_content_type() == "multipart/report" and msg_tmp.get_param("report-type", "") == "delivery-status" and msg_tmp.get("Auto-Submitted", "") == "auto-replied":
		for part in msg_tmp.walk():
			if part and part.get_content_type() == 'message/delivery-status':
				part_str = part.get_payload(decode = 0)
				for subpart in part_str:
					if subpart.get("Action", "") in ("relayed", "delivered", "expanded"):
						logging.info ("Successful DSN from " + bm_to_address)
						lib.user.GWUser(bm = bm_to_address).setlastrelay(lastrelay = time.time())
						delete_email(k)
						return

	msg_body = ''
	body_raw = ''
	decrypt_ok = False
	sigverify_ok = False

	# DKIM
	ar = msg_tmp.get_param("dkim", "missing", "Authentication-Results")
	if ar == "missing":
		try:
			domain = msg_sender.split("@")[-1]
			if lib.user.GWDomain(domain).check() and domain == msg_tmp.get_param("d", "missing", "DKIM-Signature"):
				ar = "pass" # we trust MTA to reject fakes
		except:
			pass

	## inline PGP
	for part in msg_tmp.walk():
		if part and part.get_content_type() == 'text/plain' and not (part.has_key("Content-Disposition") and part.__getitem__("Content-Disposition")[:11] == "attachment;"):
			part_str = part.get_payload(decode=1)
			if userdata.flags & 1 == 1:
				pgpparts = part_str.split("-----")
				state = 0
				pgp_body = ""
				for pgppart in pgpparts:
					if pgppart == "BEGIN PGP MESSAGE":
						pgp_body = "-----" + pgppart + "-----"
						state = 1
					elif pgppart == "END PGP MESSAGE":
						pgp_body += "-----" + pgppart + "-----"
						decrypted, sigverify_ok = lib.gpg.decrypt_content(pgp_body, msg_sender, msg_recipient)
						if isinstance(decrypted, basestring):
							part_str = decrypted
							decrypt_ok = True
						#else:
							#part_str = part.get_payload(decode = 0)
						sigresult = "fail"
						if sigverify_ok:
							sigresult = "ok"
						logging.info("Decrypted email from " + msg_sender + " to " + msg_recipient + ", signature: " + sigresult)
						state = 0
					elif pgppart == "BEGIN PGP SIGNED MESSAGE":
						pgp_body += "-----" + pgppart + "-----"
						state = 2
					elif pgppart == "BEGIN PGP SIGNATURE":
						pgp_body += "-----" + pgppart + "-----"
						state = 3
					elif pgppart == "END PGP SIGNATURE":
						pgp_body += "-----" + pgppart + "-----"
						plain, sigverify_ok = lib.gpg.verify(pgp_body, msg_sender, msg_recipient)
						if isinstance(plain, basestring):
							part_str = plain
						#else:
							#part_str = part.get_payload(decode = 0)
						sigresult = "fail"
						if sigverify_ok:
							sigresult = "ok"
						logging.info("Verifying PGP signature from " + msg_sender + " to " + msg_recipient + ": " + sigresult)
						state = 0
					elif state == 0:
						if part.get_content_charset():
							msg_body += pgppart.decode(part.get_content_charset())
						else:
							charset = chardet.detect(pgppart)
							if charset['encoding']:
								msg_body += pgppart.decode(charset['encoding'])
							else:
								msg_body += pgppart.decode('ascii')
					elif state > 0:
						pgp_body += pgppart
			else:
				if "BEGIN PGP MESSAGE" in part_str:
					decrypted, sigverify_ok = lib.gpg.decrypt_content(part_str, msg_sender, msg_recipient)
					if isinstance(decrypted, basestring):
						part_str = decrypted
						decrypt_ok = True
					else:
						part_str = part.get_payload(decode = 0)
					logging.info("Decrypted email from " + msg_sender + " to " + msg_recipient)
				elif "BEGIN PGP SIGNED MESSAGE" in part_str:
					plain, sigverify_ok = lib.gpg.verify(part_str, msg_sender, msg_recipient)
					if isinstance(plain, basestring):
						part_str = plain
					else:
						part_str = part.get_payload(decode = 0)
			body_raw += part.as_string(False)
			#print part.get_content_charset()
			#print msg_tmp.get_charset()
			if part.get_content_charset():
				try:
					part_str = part_str.decode(part.get_content_charset())
				except:
					charset = chardet.detect(part_str)
					part_str = part_str.decode(charset['encoding'])
			msg_body += part_str
	
	## if there's no plaintext content, convert the html
	if not msg_body:
		for part in msg_tmp.walk():
			if part and part.get_content_type() == 'text/html' and not (part.has_key("Content-Disposition") and part.__getitem__("Content-Disposition")[:11] == "attachment;"):
				part_str = part.get_payload(decode=1)
				h = html2text.HTML2Text()
				h.inline_links = False
				if part.get_content_charset():
					msg_body += h.handle(part_str.decode(part.get_content_charset()))
				else:
					charset = chardet.detect(part_str)
					msg_body += h.handle(part_str.decode(charset['encoding']))
				#msg_body = msg_body + html2text.html2text(unicode(part.get_payload(), part.get_content_charset()))		
	
	## if there's no plaintext or html, check if it's encrypted
	# PGP/MIME
	has_encrypted_parts = False
	if not msg_body:
		for part in msg_tmp.walk():
			if part.get_content_type() == 'application/pgp-encrypted':
				has_encrypted_parts = True

			## look for encrypted attachment containing text
			if part.get_content_type() == 'application/octet-stream' and has_encrypted_parts:
				part_str = part.get_payload(decode=1)

				## if we see the pgp header, decrypt
				if 'BEGIN PGP MESSAGE' in part_str:
					decrypted_data, sigverify_ok = lib.gpg.decrypt_content(part_str, msg_sender, msg_recipient, True)

					## decrypt failed
					if not decrypted_data:
						logging.error("Decryption of email destined for " + msg_recipient + " failed")
						msg_body += part.get_payload(decode=0)
						continue

					logging.info("Decrypted email from " + msg_sender + " to " + msg_recipient)
					msg_body += decrypted_data
					decrypt_ok = True
				elif "BEGIN PGP SIGNED MESSAGE" in part_str:
					plain, sigverify_ok = lib.gpg.verify(part_str, msg_sender, msg_recipient)
					if isinstance(plain, basestring):
						msg_body += plain
					else:
						msg_body += part.get_payload(decode = 0)
				
				## unknown attachment
				else:
					logging.debug("Received application/octet-stream type in inbound email, but did not see encryption header")

	if not sigverify_ok:
		for part in msg_tmp.walk():
			if part.get_content_type() == 'application/pgp-signature':
				plain, sigverify_ok = lib.gpg.verify(body_raw, msg_sender, msg_recipient, part.get_payload(decode=1))

	if userdata.attachments == 1 and not userdata.expired():
		for part in msg_tmp.walk():
			if part.has_key("Content-Disposition") and part.__getitem__("Content-Disposition")[:11] == "attachment;":
				# fix encoding
				try:
					filename = email.header.decode_header(part.get_filename())
					encoding = filename[0][1]
					filename = filename[0][0]
				except:
					filename = part.get_filename()
					encoding = False

				file_id, link = lib.bmmega.mega_upload(userdata.bm, filename, part.get_payload(decode = 1))
				if encoding:
					filename = unicode(filename, encoding)
				logging.info("Attachment \"%s\" (%s)", filename, part.get_content_type())
				msg_body = "Attachment \"" + filename + "\" (" + part.get_content_type() + "): " + link + "\n" + msg_body

	if not decrypt_ok:
		msg_body = "WARNING: PGP encryption missing or invalid. The message content could be exposed to third parties.\n" + msg_body
	if not sigverify_ok:
		msg_body = "WARNING: PGP signature missing or invalid. The authenticity of the message could not be verified.\n" + msg_body
	if not ar[0:4] == "pass":
		msg_body = "WARNING: DKIM signature missing or invalid. The email may not have been sent through legitimate servers.\n" + msg_body

	logging.info('Incoming email from %s to %s', msg_sender, msg_recipient)

	if SendBM(bm_from_address, bm_to_address,
		'MAILCHUCK-FROM::' + msg_sender + ' | ' + msg_subject.encode('utf-8'),
		msg_body.encode('utf-8')).status:
		## remove email file
		if userdata.archive == 1:
			#print msg_body
			save_email(k)
		else:
			delete_email(k)