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")
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()
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()
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)
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
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
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)
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)
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
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
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)
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
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)
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
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 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 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
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
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)
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)
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
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)
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)
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)
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
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
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)
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
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)
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 is_banned_username(username): if username in BMConfig().get("bmgateway", "banned_usernames"): return True else: return False
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")