예제 #1
0
def check_bmoutbox(intcond):
	global interrupted
	## get all messages
	#all_messages = json.loads(api['conn'].getAllInboxMessages())['inboxMessages']	
	logging.info("Entering BM outbox checker loop")
	intcond.acquire()
	while not interrupted:
		all_messages = get_outbox()

		logging.info("Trashing old outbox messages")
		## if no messages
		if not all_messages:
			try:
				intcond.wait(BMConfig().get("bmgateway", "bmgateway", "outbox_process_interval"))
			except KeyboardInterrupt:
				break
			continue

		## loop through messages to find unread
		for a_message in all_messages:
			if a_message['status'] == 'ackreceived':
				userdata = lib.user.GWUser(bm = a_message['toAddress'])
				if userdata:
					userdata.setlastackreceived(a_message['lastActionTime'])
				BMMessage.deleteStatic(a_message['msgid'], folder = "outbox")

		logging.info("Vacuuming DB")
		result = BMAPI().conn().deleteAndVacuum()

		intcond.wait(BMConfig().get("bmgateway", "bmgateway", "outbox_process_interval"))
	intcond.release()
	logging.info("Leaving BM outbox checker loop")
예제 #2
0
def check_emails(intcond=None):
	## find new messages in folders
	mdir = os.listdir(BMConfig().get("bmgateway", "bmgateway", "mail_folder"))

	## no new mail
	if not mdir:
		return

	## iterate through new messages, each in thread so that crashes do not prevent continuing
	for k in mdir:
		try:
			atime = os.stat(os.path.join(BMConfig().get("bmgateway", "bmgateway", "mail_folder"), k)).st_atime
		except:
			continue
		else:
			if atime > time.time() - 10:
				continue
		fhandle = open (os.path.join(BMConfig().get("bmgateway", "bmgateway", "mail_folder"), k), 'r')
		try:
			fcntl.flock(fhandle, fcntl.LOCK_EX|fcntl.LOCK_NB)
		except IOError:
			fhandle.close()
			# locked, skip this time
			continue
		email_thread = threading.Thread(target=handle_email, name="EmailIn", args=(k,))
		email_thread.start()
		email_thread.join()
		fcntl.flock(fhandle, fcntl.LOCK_UN)
		fhandle.close()
예제 #3
0
def gpg_init():
    global gpg
    global gpgme
    os.environ["GNUPGHOME"] = BMConfig().get("bmgateway", "pgp", "home")
    gpg = gnupg.GPG(gnupghome=BMConfig().get("bmgateway", "pgp", "home"),
                    verbose=False)
    gpgme = pyme.core.Context()
예제 #4
0
def save_email(k):
	savedir = BMConfig().get("bmgateway", "bmgateway", "mail_folder") + "../cur/"
	try:
		os.rename(BMConfig().get("bmgateway", "bmgateway", "mail_folder") + k,
			savedir + k)
	except OSError:
		logging.error('Could not save email: ' + BMConfig().get("bmgateway", "bmgateway", "mail_folder") + k + " to " + savedir)
예제 #5
0
def mega_login():
	mega = Mega({'verbose': False})
	try:
		m = mega.login(BMConfig().get("bmgateway", "mega", "username"), BMConfig().get("bmgateway", "mega", "password"))
	except:
		logging.error ("Mega login error")
		return None
	return m
예제 #6
0
def read_email(k):
	try:
		f = open(BMConfig().get("bmgateway", "bmgateway", "mail_folder") + k, 'r')
		message = f.read()
		return message
	except IOError:
		logging.error('Could not read email: ' + BMConfig().get("bmgateway", "bmgateway", "mail_folder") + k)
		return
예제 #7
0
def bitcoind_importaddress (address):
	bitcoinrpc = jsonrpclib.Server('http://' + 
		BMConfig().get("bmgateway", "bitcoind", "username") + ":" +
		BMConfig().get("bmgateway", "bitcoind", "password") + "@" +
		BMConfig().get("bmgateway", "bitcoind", "host") + ":" +
		str(BMConfig().get("bmgateway", "bitcoind", "port")) + "/")
	# returns null on success
	rpcreply = bitcoinrpc.importaddress(address, "", False)
예제 #8
0
 def __init__(self):
     self.logger = logging.getLogger()
     if BMConfig().get("bmgateway", "bmgateway", "debug"):
         self.logger.setLevel(logging.DEBUG)
     else:
         self.logger.setLevel(logging.INFO)
     self.handler = logging.handlers.WatchedFileHandler(BMConfig().get(
         "bmgateway", "bmgateway", "log_filename"))
     self.formatter = logging.Formatter(
         '%(asctime)s [%(threadName)s] %(levelname)s: %(message)s')
     self.handler.setFormatter(self.formatter)
     self.logger.addHandler(self.handler)
예제 #9
0
def download_key(address):
    seen = {}
    imported = False
    for server in BMConfig().get("pgpkeyservers"):
        ## try to grab key
        try:
            soup = BeautifulSoup(
                urllib2.urlopen(
                    BMConfig().get("pgpkeyservers", server, "url") +
                    BMConfig().get("pgpkeyservers", server, "begin") +
                    urllib.quote(address) +
                    BMConfig().get("pgpkeyservers", server, "end")).read())

            ## extract key result
            key_url = ''
            for item in soup(text=re.compile(r'pub ')):
                for key_link in item.parent('a'):
                    key_url = key_link.get('href')
                    if key_url in seen:
                        continue
                    seen[key_url] = True
                    key_url = BMConfig().get("pgpkeyservers", server,
                                             "url") + key_url
                    key = ''
                    try:
                        key_soup = BeautifulSoup(urllib2.urlopen(key_url))
                        key = key_soup.find('pre').getText()
                        if not key:
                            continue
                        if import_key(key):
                            imported = True
                    except urllib2.URLError, e:
                        if e == 'HTTP Error 404: Not found':
                            continue
                        else:
                            logging.error(
                                'PGP keyfinder encountered an error when contacting the '
                                + server + ' keyserver: ' + str(e))
                            continue
        ## if there is an error
        except urllib2.URLError, e:

            ## no key available, so return
            if e == 'HTTP Error 404: Not found':
                continue

            ## something went wrong!
            else:
                logging.error(
                    'PGP keyfinder encountered an error when contacting the ' +
                    server + ' keyserver: ' + str(e))
                continue
예제 #10
0
    def edit_fnc(self, status, args, out):
        #		print "[-- Response --]"
        #		out.seek(0,0)
        #		print out.read(),
        #		print "[-- Code: %d, %s --]" % (status, args)
        out.seek(0, 0)

        if args == "keyedit.prompt":
            if self.steps[self.step] == "expired_keynum":
                #sub:u:4096:1:39044548FE31C29F:1428010312:1428615112:::

                keynum = 0
                result = "help"

                for line in out.read().splitlines():
                    items = line.split(":")
                    if items[0] == "sub":
                        keynum += 1
                        if int(items[6]) + BMConfig().get(
                                "bmgateway", "pgp",
                                "delete_expired_delay") < time.time():
                            print "Key " + str(keynum) + " is expired"
                            result = "key " + str(keynum)
                            break
            else:
                result = self.steps[self.step]
            self.step += 1
        elif args == "keyedit.save.okay":
            result = "Y"
        elif args == "keyedit.remove.subkey.okay":
            result = "Y"
        elif args == "keyedit.revoke.subkey.okay":
            result = "Y"
        elif args == "ask_revocation_reason.code":
            result = "3"
        elif args == "ask_revocation_reason.text":
            result = ""
        elif args == "ask_revocation_reason.okay":
            result = "Y"
        elif args == "keygen.algo":
            result = "6"
        elif args == "keygen.size":
            result = "4096"
        elif args == "keygen.valid":
            result = BMConfig().get("bmgateway", "pgp", "expire_subkey")
        else:
            result = None

        return result
예제 #11
0
def upload_key(key):
    global gpg
    uploaded = 0
    for server in BMConfig().get("pgpkeyservers"):
        server_url = BMConfig().get("pgpkeyservers", server,
                                    "url").split("/")[2]
        try:
            result = gpg.send_keys(server_url, key.subkeys[0].fpr)
            uploaded += 1
            logging.debug('Uploading PGP key to ' + server_url + ' : ' +
                          str(result))
        except:
            logging.error('Uploading PGP key to ' + server_url + ' fail')

    return (uploaded > 0)
예제 #12
0
def check_key(address, whatreturn="keyid", operation="any", expired=False):
    global gpgme
    #gpgme.op_keylist_start(address, 0)
    if BMConfig().get("bmgateway", "bmgateway", "outgoing_thread") == 0:
        key_from_mysql(address)
    #gpgme.set_keylist_mode(pyme.constants.KEYLIST_MODE_LOCAL | pyme.constants.KEYLIST_MODE_EXTERN)
    for i in range(0, 1):
        for key in gpgme.op_keylist_all(address, 0):
            if (key.expired and not expired) or key.disabled or key.revoked:
                continue
            # TODO differentiate signing and encryption
            for subkey in key.subkeys:
                if (not expired and not subkey.expired
                    ) and not subkey.disabled and not subkey.revoked and (
                        operation == "any" or
                        (operation == "encrypt" and subkey.can_encrypt) or
                        (operation == "sign" and subkey.can_sign)):
                    if whatreturn == "keyid":
                        return subkey.keyid
                    elif whatreturn == "fpr":
                        return subkey.fpr
                    elif whatreturn == "key":
                        return key
                    else:
                        return subkey
        if i == 0 and address and not download_key(address):
            break
    return False
예제 #13
0
def delete_address(address):
	## try to delete and don't worry about if it actually goes through
	BMAPI().conn().deleteAddressBookEntry(address)
	BMAPI().conn().deleteAddress(address)
	lib.user.GWUser(bm = address).delete()

	if BMConfig().get("bmgateway", "bmgateway", "debug"):
		logging.debug('Deleted bitmessage address, ' + address)
예제 #14
0
    def handle(self):
        # self.request is the TCP socket connected to the client
        api = xmlrpclib.ServerProxy(
            'http://' + BMConfig().get("bmapi", "bm1", "username") + ':' +
            BMConfig().get("bmapi", "bm1", "password") + '@' +
            BMConfig().get("bmapi", "bm1", "host") + ':' +
            str(BMConfig().get("bmapi", "bm1", "port")) + '/')

        closed = 0
        while not closed:
            try:
                rq = lib.netstring.readns(self.request)
                #print rq
                table, key = rq.split(" ", 1)
            except:
                lib.netstring.writens(self.request, "TEMP Network error")
                closed = 1
                break

            key = key.lower()
            enckey = base64.b64encode(key)

            print "Searching for " + key + " in " + table

            #out = api.listAddressBookEntries(enckey)
            #pprint.pprint(out)
            ret = 0
            try:
                bmaddrs = json.loads(
                    api.listAddressBookEntries(enckey))['addresses']
                for address in bmaddrs:
                    if base64.b64decode(address['label']) == key:
                        ret = address['address']
            except:
                ret = 1

            try:
                if ret == 0:
                    lib.netstring.writens(self.request, "NOTFOUND")
                elif ret == 1:
                    lib.netstring.writens(self.request, "TEMP Network error")
                else:
                    lib.netstring.writens(self.request, "OK bmgateway")
            except:
                closed = 1
                break
예제 #15
0
	def extract_sender(self):
		self.sender = self.headers["From"]

		## DSN or missing sender
		if self.sender == '<>' or not self.sender:
			self.sender = BMConfig().get("bmgateway", "bmgateway", "relay_address_label")
		else:
			self.sender = re.findall(r'[\w\.+-]+@[\w\.-]+.[\w]+', self.sender)[0]
		self.sender = self.sender.lower()

		self.body = email.message_from_string(self.raw)

		if self.body.get_content_maintype == "multipart":
			self.multipart = True
			self.maintype = self.body.get_content_maintype()
			self.subtype = self.body.get_content_subtype()
		self.charset = self.body.get_content_charset()
예제 #16
0
	def read_from_file(self, fname):
		try:
			fullpath = os.path.join(BMConfig().get("bmgateway", "bmgateway", "mail_folder"), fname)
			f = open(fullpath, 'r')
			self.raw = f.read()
			f.close()
		except IOError:
			logging.error('Could not read email from: ' + fullpath)
예제 #17
0
def field_in_list(message, address_list,
		message_field, list_field):
	result = False
	try:
		if message[message_field] == address_list[BMConfig().get("bmgateway", "bmgateway", list_field)]:
			result = True
	except:
		result = False
	return result
예제 #18
0
def inotify_incoming_emails():
	mdir = BMConfig().get("bmgateway", "bmgateway", "mail_folder")

	wm = pyinotify.WatchManager()
	notifier = pyinotify.ThreadedNotifier(wm, InotifyEventHandler())
	notifier.setName ("Inotify")
	wm.add_watch (mdir, pyinotify.IN_CREATE|pyinotify.IN_CLOSE_WRITE|pyinotify.IN_MOVED_TO)
	#wm.add_watch (mdir, pyinotify.ALL_EVENTS, rec=True)
	return notifier
예제 #19
0
 def deleteStatic(msgid, folder="inbox"):
     if folder == "inbox":
         result = BMAPI().conn().trashMessage(msgid)
     elif folder == "outbox":
         result = BMAPI().conn().trashSentMessage(msgid)
     if BMConfig().get("bmgateway", "bmgateway", "debug"):
         logging.debug('Deleted bitmessage %s from %s, API response: %s',
                       msgid, folder, result)
     else:
         logging.info('Deleted bitmessage %s from %s', msgid, folder)
예제 #20
0
def create_key(address):
    ## generate key
    time_start = time.time()
    config = BMConfig()
    input_data = gpg.gen_key_input(name_email=address,
                                   name_real=address,
                                   name_comment='Generated by mailchuck.com',
                                   key_type="RSA",
                                   key_length=4096,
                                   expire_date=config.get(
                                       "bmgateway", "pgp", "expire"))
    try:
        key = gpg.gen_key(input_data)
    except:
        return False
    time_stop = time.time()
    time_total = int(time_stop - time_start)
    logging.debug('Generated PGP key for ' + address + ' in ' +
                  str(time_total) + ' seconds')

    ## upload key
    keyid = check_key(address, whatreturn="keyid", operation="any")
    return (upload_key(keyid) > 0)
예제 #21
0
    def add(self, bm, email, postmap=None):
        cur = BMMySQL().conn().cursor()
        if postmap == None:
            postmap = pwd.getpwuid(os.getuid())[0]
        trash, domain = email.split("@")
        filterwarnings('ignore', category=MySQLdb.Warning)
        pgp = 1 if BMConfig().get("bmgateway", "default", "pgp") else 0
        attachments = 1 if BMConfig().get("bmgateway", "default",
                                          "attachments") else 0
        cur.execute(
            """INSERT IGNORE INTO user (bm, email, postmap, domain, pgp, credits, exp, active, cansend, cancharge, caninvoice, attachments, html, lastackreceived)
				VALUES (%s, %s, %s, %s, %s, '0', '1971-01-01', 1, 1, 0, 0, %s, 0, UNIX_TIMESTAMP(NOW()))""",
            (bm, email, postmap, domain, pgp, attachments))
        uid = None
        if cur.rowcount == 1:
            uid = cur.lastrowid
            self.load(uid=uid)
            logging.info('Registered new user (%u) %s', uid, email)
        else:
            logging.error(
                'Failed to add new user entry into the database for %s', email)
        cur.close()
        return uid
예제 #22
0
def create_key(address):
	## generate key
	time_start = time.time()
	config = BMConfig()
	input_data = gpg.gen_key_input(name_email=address, name_real=address, name_comment='Generated by mailchuck.com', key_type="RSA", key_length=4096, expire_date=config.get("bmgateway", "pgp", "expire"))
	try:
		key = gpg.gen_key(input_data)
	except:
		return False
	time_stop = time.time()
	time_total = int(time_stop - time_start)
	logging.debug('Generated PGP key for ' + address + ' in ' + str(time_total) + ' seconds')

	## upload key
	keyid = check_key(address, whatreturn="keyid", operation="any")
	return (upload_key(keyid) > 0)
예제 #23
0
def create_primary_key(address):
    global gpgme
    gpgme.set_armor(1)
    params = """<GnupgKeyParms format="internal">
	Key-Type: RSA
	Key-Length: 4096
	Key-Usage: sign,auth
	Name-Real: """ + address + """
	Name-Email: """ + address + """
	Name-Comment: Generated by mailchuck
	Expire-Date: """ + BMConfig().get("bmgateway", "pgp", "expire") + """
	</GnupgKeyParms>
	"""
    gpgme.op_genkey(params, None, None)
    key = check_key(address, whatreturn="key", operation="sign")
    key_to_mysql(key)
    return upload_key(key)
예제 #24
0
	def __init__(self, maps, base):
		# load template source
		src = None
		try:
			with open(os.path.join(os.path.dirname(__file__), '..', 'templates',  base + '.txt')) as tempsrc:
				src = email.message_from_file(tempsrc)
		except:
			return None

		# init general maps
		maps['timestamp'] = time.strftime("Generated at: %b %d %Y %H:%M:%S GMT", time.gmtime())
		maps['domain'] = BMConfig().get("bmgateway", "bmgateway", "domain_name")
		maps['relayaddress'] = BMAPI().get_address(BMConfig().get("bmgateway", "bmgateway", "relay_address_label"))
		maps['deregisteraddress'] = BMAPI().get_address(BMConfig().get("bmgateway", "bmgateway", "deregistration_address_label"))
		maps['registeraddress'] = BMAPI().get_address(BMConfig().get("bmgateway", "bmgateway", "registration_address_label"))
		maps['bugreportemail'] = BMConfig().get("bmgateway", "bmgateway", "bug_report_address_email")
		maps['bugreportaddress'] = BMConfig().get("bmgateway", "bmgateway", "bug_report_address_bitmessage")
		maps['mailinglistaddress'] = BMConfig().get("bmgateway", "bmgateway", "broadcast_address_bitmesage")
		maps['companyname'] = BMConfig().get("bmgateway", "bmgateway", "companyname")
		maps['companyaddress'] = BMConfig().get("bmgateway", "bmgateway", "companyaddress")

		# BTC URI
		if 'btcuri' in maps:
			addr = re.search('^bitcoin:([^?]+)(\?(.*))?', maps['btcuri'])
			if addr:
				maps['btcaddress'] = addr.group(1)
				attr = urlparse.parse_qs(addr.group(3))
				if 'amount' in attr:
					maps['btcamount'] = attr['amount'][0]
			maps['qrbtcuri'] = lib.payment.qrcode_encoded(maps['btcuri'])

		subst = string.Template(src.get_payload()).safe_substitute(maps)
		self.body = subst.replace('\n', '\r\n')

		if src.has_key("Subject"):
			self.subject = string.Template(src.get("Subject")).safe_substitute(maps)
예제 #25
0
def mega_upload(bm, fname, data):
	m = mega_login()
	if m == None:
		return None, None
	foldername = BMConfig().get("bmgateway", "mega", "folder")
	folder = m.find(foldername)
	loops = 30
	while folder == None and loops > 0:
		try:
			m.create_folder(foldername)
			folder = m.find(foldername)
		except:
			pass
		if folder == None:
			time.sleep (1)
		loops -= 1
	if folder == None:
		return None, None

	
	uploadedfile = None
	loops = 30
	while uploadedfile == None and loops > 0:
		try:
			uploadedfile = m.upload(data, folder[0], dest_filename=fname, save_key=False)
		except:
			pass
		if uploadedfile == None:
			time.sleep (1)
		loops -= 1
		
	file_id = uploadedfile['f'][0]['h']
	link = m.get_upload_link(uploadedfile)
	cur = BMMySQL().conn().cursor()
	cur.execute ("INSERT IGNORE INTO mega (fileid, bm) VALUES (%s, %s)", (
		file_id, bm))
	cur.close()
	return file_id, link
예제 #26
0
	def connect(self):
		orig_conv = MySQLdb.converters.conversions
		#Adding support for bit data type
		orig_conv[FIELD_TYPE.BIT] = bool

		for mysql in BMConfig().get("mysql"):
			if BMConfig().get("mysql", mysql, "unix_socket"):
				try:
					self.thrdata.db =  MySQLdb.connect(unix_socket = BMConfig().get("mysql", mysql, "unix_socket"),
						user = BMConfig().get("mysql", mysql, "user"),
						passwd = BMConfig().get("mysql", mysql, "passwd"),
						db = BMConfig().get("mysql", mysql, "db"), conv = orig_conv)
					return self.thrdata.db
				except MySQLdb.Error, e:
					print "MySQLdb.Error is %d: %s" % (e.args[0], e.args[1])
					continue
				except:
					print "Error connecting to " + mysql
예제 #27
0
    def connect(self):
        for bm in BMConfig().get("bmapi"):
            self.thrdata.bm = xmlrpclib.ServerProxy(
                'http://' + BMConfig().get("bmapi", bm, "username") + ':' +
                BMConfig().get("bmapi", bm, "password") + '@' +
                BMConfig().get("bmapi", bm, "host") + ':' +
                str(BMConfig().get("bmapi", bm, "port")) + '/')

            ## check if API is responding
            try:
                response = self.thrdata.bm.add(2, 2)
                logging.info("Connected to Bitmessage API on %s:%i",
                             BMConfig().get("bmapi", bm, "host"),
                             BMConfig().get("bmapi", bm, "port"))
                break
            except:
                self.thrdata.bm = None
        if self.thrdata.bm is not None:
            return self.thrdata.bm
        else:
            logging.error('Could not connect to Bitmessage API ')
        return False
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)
예제 #29
0
class EmailParser(object):
	def __init__ (self):
		self.raw = None
		self.body = None
		self.subject = None
		self.sender = None
		self.recipient = None
		self.recipientbm = None
		self.pgpbody = None
		self.headers = {}
		self.signature = False
		self.encryption = False
		self.dkim = False
		self.status = False
		self.multipart = False
		self.maintype = None
		self.subtype = None
		self.charset = None
		self.attachment = None
		self.dsn = None
		self.parent = None
		self.out = None

	def parse(self):
		if self.parent == None:
			try:
				self.parse_headers()
			except:
				return False
		try:
			self.parse_body()
		except:
			return False

		self.subject = base64.b64encode(subject)
		self.body = base64.b64encode(body)
		if (sender[0:3] == "BM-"):
			senderbm = sender
		else:
			senderbm = BMAPI().get_address(sender)
		recipientbm = recipient
		userdata = lib.user.GWUser(bm = recipient)
		if userdata.check():
			recipient = userdata['email']
		try:
			ackData = BMAPI().conn().sendMessage(recipientbm, senderbm, self.subject, self.body, 2)
			logging.info("Sent BM from %s to %s", sender, recipient)
		except:
			logging.error("Failure sending BM from %s to %s", sender, recipient)
			return False
		return True

	def read_from_file(self, fname):
		try:
			fullpath = os.path.join(BMConfig().get("bmgateway", "bmgateway", "mail_folder"), fname)
			f = open(fullpath, 'r')
			self.raw = f.read()
			f.close()
		except IOError:
			logging.error('Could not read email from: ' + fullpath)

	def from_data(self, data):
		self.raw = data

	def parse_headers(self):
		self.headers = email.parser.Parser().parsestr(self.raw)
		if not self.headers:
			logging.error('Email missing headers')
			raise
		self.extract_sender()
		self.extract_recipient()
		if not self.sender or not self.recipient:
			logging.warn('Email missing sender or recipient')
			raise
		self.extract_subject()
		self.extract_dkim()

	def extract_sender(self):
		self.sender = self.headers["From"]

		## DSN or missing sender
		if self.sender == '<>' or not self.sender:
			self.sender = BMConfig().get("bmgateway", "bmgateway", "relay_address_label")
		else:
			self.sender = re.findall(r'[\w\.+-]+@[\w\.-]+.[\w]+', self.sender)[0]
		self.sender = self.sender.lower()

		self.body = email.message_from_string(self.raw)

		if self.body.get_content_maintype == "multipart":
			self.multipart = True
			self.maintype = self.body.get_content_maintype()
			self.subtype = self.body.get_content_subtype()
		self.charset = self.body.get_content_charset()

	def attachment(

		if self.body.has_key("Content-Disposition") and self.body.__getitem__("Content-Disposition")[:11] == "attachment":
			self.attachment = email.header.decode_header(part.get_filename())[0]
			
	def extract_recipient(self):
		## find email details
		userdata = None
		rcpts = ()
		for rcpthdr in ("To", "X-Original-To", "Cc"):
			rcpts.extend(re.findall(r'[\w\.+-]+@[\w\.-]+.[\w]+', self.headers[rcpthdr]))
		for candidate in rcpts:
			## strip extension (user+foo@domain)
			self.recipient = re.sub(r'\+.*@', '@', candidate) 
			## lowercasse
			self.recipient = self.recipient.lower()
			## check if user exists
			userdata = lib.user.GWUser(email = self.recipient, unalias = True)
			if userdata.check():
				break

	def extract_subject(self):
		self.subject = email.header.decode_header(self.headers['Subject'])[0]
		if(self.subject[1]):
			self.subject = unicode(self.subject[0], self.subject[1])
		else:
			self.subject = self.subject[0]

	def extract_dkim(self):
		ar = self.body.get_param("dkim", "missing", "Authentication-Results")
		if ar == "missing":
			domain = self.sender.split("@")[-1]
			if lib.user.GWDomain(domain).check() and domain == self.body.get_param("d", "missing", "DKIM-Signature"):
				ar = "pass" # we trust MTA to reject fakes from domains that hare handled locally
		if ar[0:4] == "pass":
			self.dkim = True
	
	def parse_body(self)
		cipher = None
		signature = None
		if self.multipart:
			for part in self.body.walk():
				if 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") and
							self.body.get_param("report-type", "") == "delivery-status" and self.body.get("Auto-Submitted", "") == "auto-replied":
							self.dsn = True
				elif part.get_content_type() == 'text/plain' and
					(self.subtype != "alternative" or self.out == None): # lower precedence if other content exists
					self.handle_text(part)
				elif part.get_content_type() == 'message/rfc822':
					self.handle_text(part)
				elif part.get_content_type() == 'text/html':
					self.handle_html(part)
				elif part.has_key("Content-Disposition") and part.__getitem__("Content-Disposition")[:11] == "attachment;":
					if has setting
						handle_attachment()
					
			elif self.subtype == "mixed":
			elif self.subtype == "digest":
			elif self.subtype == "alternative":
			elif self.subtype == "related":
			elif self.subtype == "signed":
			elif self.subtype == "encrypted":
		elif self.maintype == "message" and self.subtype == "rfc822":
			self.handle_text(self.body)
		elif self.maintype == "text" and self.subtype == "plain":
			self.handle_text(self.body)
		elif self.maintype == "text" and self.subtype == "html":
			self.handle_html(part)
		return True

	def handle_text(self, msg):
		text = msg.get_payload(decode = True)
		if (self.has_pgp(text)):
			self.out += self.handle_pgp(msg)
		else:
			self.out += text

	def handle_html(self, msg):
		text = msg.get_payload(decode = True)
		if (self.has_pgp(text)):
			text = self.handle_pgp(text)
		h = html2text.HTML2Text()
		h.inline_links = False
		if not msg.get_content_charset():
			charset = chardet.detect(text)
			if charset['encoding'] == None:
				charset = 'ascii'
			else:
				charset = charset['encoding']

		text = h.handle(text).decode(charset))
		self.out += text
예제 #30
0
def delete_email(k):
	try:
		os.remove(BMConfig().get("bmgateway", "bmgateway", "mail_folder") + k)
	except OSError:
		logging.error('Could not delete email: ' + BMConfig().get("bmgateway", "bmgateway", "mail_folder") + k)
예제 #31
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)
예제 #32
0
def is_banned_username(username):
	if username in BMConfig().get("bmgateway", "banned_usernames"):
		return True
	else:
		return False
예제 #33
0
def check_bminbox(intcond):
	global interrupted
	## get all messages
	#all_messages = json.loads(api['conn'].getAllInboxMessages())['inboxMessages']	
	
	logging.info("Entering BM inbox checker loop")
	intcond.acquire()
	while not interrupted:
		all_messages = get_inbox()
	
		## if no messages
		if not all_messages:
			try:
				intcond.wait(BMConfig().get("bmgateway", "bmgateway", "process_interval"))
			except KeyboardInterrupt:
				break
			continue
	
		## loop through messages to find unread
		for a_message in all_messages:
	
			## if already read, delete and break
			if a_message['read'] == 1:
				BMMessage.deleteStatic(a_message['msgid'])
				continue

			## check if already processed, maybe from another instance
			if lib.bminbox.check_message_processed(a_message['msgid']):
				logging.info('Message %s has already been processed deleting...', a_message['msgid'])
#				BMMessage.deleteStatic(a_message['msgid'])
#				continue

			## if the message is unread, load it by ID to trigger the read flag
			message = json.loads(BMAPI().conn().getInboxMessageByID(a_message['msgid'], False))['inboxMessage'][0]
	
			## if a blank message was returned
			if not message:
				logging.error('API returned blank message when requesting a message by msgID')
				delete_bitmessage_inbox(bm_id)
				BMMessage.deleteStatic(a_message['msgid'])
				continue
	
			## find message ID
			bm_id = message['msgid']
	
			## check if receive address is a DEregistration request
			if field_in_list(message, BMAPI().address_list,
				'toAddress', 'deregistration_address_label'):
	
				## check if address is registered
				userdata = lib.user.GWUser(bm = message['fromAddress'])
	
				## if the sender is actually registered and wants to deregister
				if userdata.check():
					## process deregistration
					logging.info('Processed deregistration request for user ' + userdata.email)
					delete_address(message['fromAddress'])
	
					## send deregistration confirmation email
					SendBMTemplate(
						sender = BMAPI().get_address(BMConfig().get("bmgateway", "bmgateway", "deregistration_address_label")),
						recipient = message['fromAddress'],
						template = "deregistration-confirmed",
						addmaps = {
							'email': userdata.email
						})
	
				## bogus deregistration request
				else:
					logging.warn('Purged malicious deregistration bitmessage from ' + message['fromAddress'])
	
			elif field_in_list(message, BMAPI().address_list, 'toAddress', 'bug_report_address_label'):
				userdata = lib.user.GwUser(bm = message['fromAddress'])
				# if not, create a fake one
				# relay to ticket
	
			## check if receive address is a registration request
			elif field_in_list(message, BMAPI().address_list,
				'toAddress', 'registration_address_label'):

				userdata = lib.user.GWUser(bm = message['fromAddress'])

				if userdata.check(): # status, config, etc
					command = base64.b64decode(message['subject']).lower()
					if command == "config":
						logging.info('Config request from %s', message['fromAddress'])
						body = base64.b64decode(message['message'])
						data = {}
						for line in body.splitlines():
							line = re.sub("#.*", "", line)
							option = re.search("(\S+)\s*:\s*(\S+)", line)
							if option is None:
								continue
							if option.group(1).lower() == "pgp":
								data['pgp'] = lib.user.GWUserData.pgp(option.group(2))
							elif option.group(1).lower() == "attachments":
								data['attachments'] = lib.user.GWUserData.zero_one(option.group(2))
							#elif option.group(1).lower() == "flags":
								#data['flags'] = lib.user.GWUserData.numeric(option.group(2))
							elif option.group(1).lower() == "archive":
								data['archive'] = lib.user.GWUserData.zero_one(option.group(2))
							elif option.group(1).lower() == "masterpubkey_btc":
								data['masterpubkey_btc'] = lib.user.GWUserData.public_seed(option.group(2))
								# reset offset unless set explicitly
								if data['masterpubkey_btc'] is not None and not 'offset_btc' in data:
									data['offset_btc'] = "0"
							elif option.group(1).lower() == "offset_btc":
								data['offset_btc'] = lib.user.GWUserData.numeric(option.group(2))
							elif option.group(1).lower() == "feeamount":
								data['feeamount'] = lib.user.GWUserData.numeric(option.group(2), 8)
							elif option.group(1).lower() == "feecurrency":
								data['feecurrency'] = lib.user.GWUserData.currency(option.group(2))
							else:
								pass
						if userdata.update(data):
							SendBMTemplate(
								sender = BMAPI().get_address(BMConfig().get("bmgateway", "bmgateway", "registration_address_label")),
								recipient = message['fromAddress'],
								template = "configchange",
								addmaps = {
								})
						else:
							SendBMTemplate(
								sender = BMAPI().get_address(BMConfig().get("bmgateway", "bmgateway", "registration_address_label")),
								recipient = message['fromAddress'],
								template = "confignochange",
								addmaps = {
								})
							pass
					elif command == "status" or command == "" or not command:
						logging.info('Status request from %s', message['fromAddress'])
						SendBMTemplate(
							sender = BMAPI().get_address(BMConfig().get("bmgateway", "bmgateway", "registration_address_label")),
							recipient = message['fromAddress'],
							template = "status",
							addmaps = {
								'email': userdata.email,
								'domain': userdata.domain,
								'active': "Yes" if userdata.active else "No",
								'cansend': "Yes" if userdata.cansend else "No",
								'cancharge': "Yes" if userdata.cancharge else "No",
								'caninvoice': "Yes" if userdata.caninvoice else "No",
								'pgp': "server" if userdata.pgp else "local",
								'attachments': "Yes" if userdata.attachments else "No",
								'expires': userdata.exp.strftime("%B %-d %Y"),
								'masterpubkey_btc': userdata.masterpubkey_btc if userdata.masterpubkey_btc else "N/A",
								'offset_btc': str(userdata.offset_btc) if userdata.masterpubkey_btc else "N/A",
								'feeamount': str(userdata.feeamount) if userdata.masterpubkey_btc else "N/A",
								'feecurrency': str(userdata.feecurrency) if userdata.masterpubkey_btc else "N/A",
								'archive': "Yes" if userdata.archive else "No",
								'flags': hex(userdata.flags),
								'aliases': ', '.join(userdata.aliases) if userdata.aliases else "None"
							})
					else:
						logging.info('Invalid command from %s', message['fromAddress'])
						SendBMTemplate(
							sender = BMAPI().get_address(BMConfig().get("bmgateway", "bmgateway", "registration_address_label")),
							recipient = message['fromAddress'],
							template = "command-invalid",
							addmaps = {
								'command': command,
								'email': userdata.email
							})
					
				else: # attempt to register new user
					## find requested username
					proposed_registration_user = base64.b64decode(message['subject']).lower()

					#full_registration_user = registration_user + '@' + BMConfig().get("bmgateway", "bmgateway", "domain_name")
					valid_one = re.match('^[\w]{4,20}$', proposed_registration_user) is not None
					valid_two =  re.match('^[\w]{4,20}@' + BMConfig().get("bmgateway", "bmgateway", "domain_name") + '$', proposed_registration_user) is not None
	
					# strip domain if they sent it during registration
					if valid_one:
						full_registration_user = proposed_registration_user.lower() + '@' + BMConfig().get("bmgateway", "bmgateway", "domain_name")
						registration_user = proposed_registration_user.lower()
					elif valid_two:
						full_registration_user = proposed_registration_user.lower()
						registration_user = proposed_registration_user.split('@')[0].lower()
					else:
						logging.info('Invalid email address in registration request for %s', proposed_registration_user)
						SendBMTemplate(
							sender = BMAPI().get_address(BMConfig().get("bmgateway", "bmgateway", "registration_address_label")),
							recipient = message['fromAddress'],
							template = "registration-invalid",
							addmaps = {
								'email': proposed_registration_user
							})
						BMMessage.deleteStatic(bm_id)
						continue
	
					## if username is valid check if it's available

					## check if address is already registered to a username or is banned
					if is_banned_username(registration_user):
						logging.info('Banned email address in registration request for %s', registration_user)
						SendBMTemplate(
							sender = BMAPI().get_address(BMConfig().get("bmgateway", "bmgateway", "registration_address_label")),
							recipient = message['fromAddress'],
							template = "registration-duplicate",
							addmaps = {
								'email': full_registration_user
							})
						BMMessage.deleteStatic(bm_id)
						continue
					elif lib.user.GWUser(email = full_registration_user).check():
						logging.info('Duplicate email address in registration request for %s', registration_user)
						SendBMTemplate(
							sender = BMAPI().get_address(BMConfig().get("bmgateway", "bmgateway", "registration_address_label")),
							recipient = message['fromAddress'],
							template = "registration-duplicate",
							addmaps = {
								'email': full_registration_user
							})
						BMMessage.deleteStatic(bm_id)
						continue
	
					logging.info('Received registration request for email address %s ', full_registration_user)
					lib.user.GWUser(empty = True).add(bm = message['fromAddress'], email = full_registration_user)
					SendBMTemplate(
						sender = BMAPI().get_address(BMConfig().get("bmgateway", "bmgateway", "registration_address_label")),
							recipient = message['fromAddress'],
							template = "registration-confirmed",
							addmaps = {
								'email': full_registration_user
							})
			## if sent to the generic recipient or sender address
			elif field_in_list(message, BMAPI().address_list,
				'toAddress', 'relay_address_label'):
	
				## if user is not registered, purge
				userdata = lib.user.GWUser(bm = message['fromAddress'])
				if not userdata.check():
					if BMConfig().get("bmgateway", "bmgateway", "allow_unregistered_senders"):
						bm_sender = message['fromAddress'] + '@' + BMConfig().get("bmgateway", "bmgateway", "domain_name")
					else:
						logging.warn('Purged bitmessage from non-registered user ' + message['fromAddress'])
						BMMessage.deleteStatic(bm_id)
						continue
	
				## if user is registered, find their username @ domain
				else:
					bm_sender = userdata.email

				## find outbound email address
				bm_receiver = re.findall(r'[\w\.\+-]+@[\w\.-]+\.[\w]+', base64.b64decode(message['subject']))
				if len(bm_receiver) > 0:
					bm_receiver = bm_receiver[0]
				
				## if there is no receiver mapping or the generic address didnt get a valid outbound email, deny it
				if not bm_receiver:
					# FIXME explain to sender what is whrong
					logging.warn('Received and purged bitmessage with unknown recipient (likely generic address and bad subject)')
					if BMConfig().get("bmgateway", "bmgateway", "respond_to_missing"):
						SendBMTemplate(
							sender = message['toAddress'],
							recipient = message['fromAddress'],
							template = "relay-missing-recipient",
							addmaps = {
								'email': userdata.email,
							})
					BMMessage.deleteStatic(bm_id)
					continue
	
				# expired or cannot send
				if (userdata.expired() or userdata.cansend == 0) and not \
					(bm_receiver == BMConfig().get("bmgateway", "bmgateway", "bug_report_address_email")): # can still contact bugreport
					btcaddress, amount = lib.payment.payment_exists_domain (BMConfig().get("bmgateway", "bmgateway", "domain_name"), userdata.bm)
				        # create new one
       					if btcaddress == False:
                				btcaddress, amount = lib.payment.create_invoice_domain (BMConfig().get("bmgateway", "bmgateway", "domain_name"), userdata.bm)

					SendBMTemplate(
						sender = BMAPI().get_address(BMConfig().get("bmgateway", "bmgateway", "registration_address_label")),
						recipient = message['fromAddress'],
						template = "accountexpired",
						addmaps = {
							'btcuri': lib.payment.create_payment_uri(btcaddress, 'BTC', amount,
								BMConfig().get("bmgateway", "bmgateway", "companyname"), 'User ' + userdata.bm + " / " + userdata.email + ' subscription'),
				                        'service': 'Subscription for ' + userdata.email + ' from ' + datetime.date.today().strftime("%B %-d %Y") +
								' until ' + userdata.exp.strftime("%B %-d %Y"),
							'email': userdata.email
						})
					logging.warn("User " + message['fromAddress'] + " notified of payment requirement")
					BMMessage.deleteStatic(bm_id)
					continue
	
				bm_subject = base64.b64decode(message['subject'])
	 
				## handle removal of embedded MAILCHUCK-FROM:: tag for replies
				bm_subject = bm_subject.replace('MAILCHUCK-FROM::' + bm_receiver + ' | ', '');
	
				## remove email address from subject
				if field_in_list(message, BMAPI().address_list, 'toAddress', 'relay_address_label'):
					bm_subject = bm_subject.replace(bm_receiver, '')	
	
				## get message contents
				bm_body = base64.b64decode(message['message'])	

				## pad with a newline, otherwise it may look ugly
				if bm_body[-1:] != '\n':
					bm_body += '\n'
	
				## send message and delete bitmessage, bitches
				if (float(userdata.lastrelay) + BMConfig().get("bmgateway", "bmgateway", "throttle") > time.time()):
					SendBMTemplate(
						sender = message['toAddress'],
						recipient = message['fromAddress'],
						template = "relay-throttle",
						addmaps = {
							'email': userdata.email,
							'throttledelta':  str(int((float(userdata.lastrelay) +
								BMConfig().get("bmgateway", "bmgateway", "throttle") - time.time() + 60)/60))
						})
					logging.warn('Throttled %s', message['fromAddress'])
					BMMessage.deleteStatic(bm_id)
					continue
				else:
					retval = send_email(bm_receiver, bm_sender, bm_subject, bm_body, bm_id, userdata = userdata)
					if retval is None:
						logging.info('Relayed from %s to %s', message['fromAddress'], bm_receiver)
					else:
						if retval[0] >= 400 and retval[0] < 500:
							# do not delete, repeatable
							continue
						else:
							SendBMTemplate(
								sender = message['toAddress'],
								recipient = message['fromAddress'],
								template = "smtperror",
								addmaps = {
									'emailrcpt': bm_receiver,
									'errcode': retval[0],
									'errmessage': retval[1]
								}
							)
							

			## remove message
			BMMessage.deleteStatic(bm_id)
			lib.bminbox.set_message_processed(bm_id)
		intcond.wait(BMConfig().get("bmgateway", "bmgateway", "process_interval"))
	intcond.release()
	logging.info("Leaving BM inbox checker loop")