def send(self, subject="", to="", body="", smtp=""): # TODO add "cc", "bcc" if len(to) == 0: raise Exception('send(): recipient email address is not provided') if not self.is_simulate: send_email(subject, self._imap_account.email, to, body) logger.debug("send(): sent a message to %s" % str(to))
def handle(self, *args, **options): # iterate over all the user accounts in the database imapAccounts = ImapAccount.objects.filter(is_initialized=False) for imapAccount in imapAccounts: if imapAccount.is_running: continue imapAccount.is_running = True imapAccount.save() res = {'status': False, 'imap_error': False} logger.info("run initial sync for email: %s" % imapAccount.email) # authenticate with the user's imap server auth_res = authenticate(imapAccount) # if authentication failed we can't run anything if not auth_res['status']: continue # get an imapclient which is authenticated imap = auth_res['imap'] try: # create the mailbox mailbox = MailBox(imapAccount, imap) # sync the mailbox with imap mailbox._sync() logger.info("Mailbox sync done") # after sync, logout to prevent multi-connection issue imap.logout() logger.info( "Mailbox logged out to prevent multi-connection issue") mailbox._run_user_code() except Exception: logger.exception("mailbox task running failed %s " % imapAccount.email) send_email("Your YoUPS account is ready!", "no-reply@" + BASE_URL, '*****@*****.**', "%s register inbox failed " % imapAccount.email) continue imapAccount.is_initialized = True imapAccount.is_running = False imapAccount.save() res['status'] = True
logger.exception("IMAPClient.Error - failed to authenticate email") res['imap_error'] = e res['code'] = "Can't authenticate your email" except Exception, e: logger.exception("Unknown exception %s failed to authenticate email" %e) # TODO add exception res['imap_error'] = e res['code'] = msg_code['UNKNOWN_ERROR'] if res['status'] is False: # email to the user that there is error at authenticating email if imap_account.is_oauth and len(imap_account.email) > 0: subject = "[" + WEBSITE + "] Authentication error occurs" body = "Authentication error occurs! \n" + str(res['imap_error']) body += "\nPlease log in again at " + BASE_URL + "/editor" send_email(subject, WEBSITE + "@" + BASE_URL, imap_account.email, body) # TODO don't delete # Delete this ImapAccount information so that it requires user to reauthenticate # imap_account.password = "" imap_account.access_token = "" # turn off the email engine # imap_account.is_running = False imap_account.save() return res def decrypt_plain_password(encrypted_password): aes = AES.new(IMAP_SECRET, AES.MODE_CBC, 'This is an IV456') password = aes.decrypt( base64.b64decode(encrypted_password) )
def register_inbox(): """Do the initial sync on an inbox. """ lockFile = 'register_inbox.lock' with open(lockFile, 'w') as f: have_lock = get_lock(f) if not have_lock: logger.info('Lock already taken %s' % lockFile) return for imapAccount in ImapAccount.objects.filter(is_initialized=False): try: logger.info('registering inbox: %s', imapAccount.email) while True: try: # authenticate with the user's imap server auth_res = authenticate(imapAccount) # if authentication failed we can't run anything if not auth_res['status']: # Stop doing loop # TODO maybe we should email the user logger.critical( 'register authentication failed for %s', imapAccount.email) continue # get an imapclient which is authenticated imap = auth_res['imap'] # type: IMAPClient # create the mailbox mailbox = MailBox(imapAccount, imap) # sync the mailbox with imap done = mailbox._sync() if done: break # if we catch an EOF error we continue except imaplib.IMAP4.abort: logger.exception("Caught EOF error while syncing") try: imap.logout() except Exception: logger.exception( "Failure while logging out due to EOF bug") continue # if we catch any other type of exception we abort to avoid infinite loop except Exception: logger.critical("Failure while initially syncing") logger.exception("Failure while initially syncing") raise logger.info("Mailbox sync done: %s" % (imapAccount.email)) # after sync, logout to prevent multi-connection issue imap.logout() imapAccount.is_initialized = True imapAccount.save() site = Site.objects.get_current() send_email( "Your YoUPS account is ready!", "no-reply@" + BASE_URL, imapAccount.email, "Start writing your automation rule here! %s://%s" % (PROTOCOL, site.domain)) logger.info('Register done for %s', imapAccount.email) except ImapAccount.DoesNotExist: imapAccount.is_initialized = False imapAccount.save() logger.exception( "syncing fails Remove periodic tasks. imap_account not exist %s" % (imapAccount.email)) except Exception as e: logger.exception( "User inbox syncing fails %s. Stop syncing %s" % (imapAccount.email, e))
def interpret(mailbox, mode): """This function executes users' code. Args: mailbox (Mailbox): user's mailbox mode (MailbotMode or None): current mode. if mode is null, it will bypass executing user's code and just print logs is_simulate (boolean): if True, it looks into extra_info to test run user's code extra_info (dict): it includes code, which is to be test ran, and msg-id, which is a id field of MessageSchema """ # type: (MailBox, MailbotMode, bool) -> t.Dict[t.AnyStr, t.Any] from schema.youps import EmailRule, MailbotMode # set up the default result res = {'status': True, 'imap_error': False, 'imap_log': ""} # assert we actually got a mailbox assert isinstance(mailbox, MailBox) # assert the mode is the mailboat mode # assert isinstance(mode, MailbotMode) assert mailbox.new_message_handler is not None def on_message_arrival(func): mailbox.new_message_handler += func # get the logger for user output userLogger = logging.getLogger('youps.user') # type: logging.Logger # get the stream handler associated with the user output userLoggerStreamHandlers = filter( lambda h: isinstance(h, logging.StreamHandler), userLogger.handlers) userLoggerStream = userLoggerStreamHandlers[ 0].stream if userLoggerStreamHandlers else None assert userLoggerStream is not None # create a string buffer to store stdout user_std_out = StringIO() new_log = {} # execute user code try: # set the stdout to a string sys.stdout = user_std_out # set the user logger to userLoggerStream = user_std_out from schema.youps import FolderSchema # define the variables accessible to the user user_environ = { 'create_draft': mailbox.create_draft, 'create_folder': mailbox.create_folder, 'get_email_mode': mailbox.get_email_mode, 'set_email_mode': mailbox.set_email_mode, 'send': mailbox.send, 'handle_on_message': lambda f: mailbox.new_message_handler.handle(f), 'handle_on_flag_added': lambda f: mailbox.added_flag_handler.handle(f), 'handle_on_flag_removed': lambda f: mailbox.removed_flag_handler.handle(f), 'handle_on_deadline': lambda f: mailbox.deadline_handler.handle(f), 'Calendar': MyCalendar, } # iterate through event queue for event_data in mailbox.event_data_list: # Iterate through email rule at the current mode # TODO maybe use this instead of mode.rules if mode is None: continue for rule in EmailRule.objects.filter(mode=mode): is_fired = False copy_msg = {} copy_msg["timestamp"] = str( datetime.datetime.now().strftime("%m/%d %H:%M:%S,%f")) assert isinstance(rule, EmailRule) # TODO why the reassignment of valid folders valid_folders = [] code = rule.code # logger.info(code) # add the user's functions to the event handlers # basically at the end of the user's code we need to attach the user's code to # the event # user code strings can be found at http_handler/static/javascript/youps/login_imap.js ~ line 300 # our handlers are in mailbox and the user environment if rule.type.startswith("new-message"): valid_folders = FolderSchema.objects.filter( imap_account=mailbox._imap_account, rules=rule) code = code + "\nhandle_on_message(on_message)" elif rule.type == "flag-change": code = code + "\nhandle_on_flag_added(on_flag_added)" code = code + "\nhandle_on_flag_removed(on_flag_removed)" elif rule.type.startswith("deadline"): valid_folders = FolderSchema.objects.filter( imap_account=mailbox._imap_account).filter( is_selectable=True) code = code + "\nhandle_on_deadline(on_deadline)" # else: # continue # # some_handler or something += repeat_every try: # execute the user's code # exec cant register new function (e.g., on_message_arrival) when there is a user_env # logger.exception(rule.id) # logger.exception(code) exec(code, user_environ) # TODO this should be cleaned up. accessing class name is ugly and this is very wet (Not DRY) if event_data.message._schema.folder in valid_folders: event_class_name = type(event_data).__name__ if (event_class_name == "MessageArrivalData" and rule.type =="new-message") or \ (event_class_name == "NewMessageDataScheduled" and rule.type.startswith("new-message-")): is_fired = True event_data.fire_event(mailbox.new_message_handler) if (event_class_name == "NewFlagsData" and rule.type == "flag-change"): is_fired = True event_data.fire_event(mailbox.added_flag_handler) if (event_class_name == "RemovedFlagsData" and rule.type == "flag-change"): is_fired = True event_data.fire_event(mailbox.removed_flag_handler) if (event_class_name == "NewMessageDataDue" and rule.type.startswith("deadline")): is_fired = True event_data.fire_event(mailbox.deadline_handler) if is_fired: logger.debug( "firing %s %s" % (rule.name, event_data.message.subject)) except Exception as e: # Get error message for users if occurs # print out error messages for user # if len(inspect.trace()) < 2: # logger.exception("System error during running user code") # else: exc_type, exc_obj, exc_tb = sys.exc_info() logger.exception("failure running user %s code" % mailbox._imap_account.email) error_msg = str(e) + traceback.format_tb(exc_tb)[-1] try: send_email( "failure running user %s code" % mailbox._imap_account.email, "*****@*****.**", "*****@*****.**", error_msg.decode('utf-8'), error_msg.decode('utf-8')) except Exception: logger.exception("Can't send error emails to admin :P") copy_msg["log"] = error_msg copy_msg["error"] = True finally: if is_fired: copy_msg.update(print_execution_log( event_data.message)) logger.debug("handling fired %s %s" % (rule.name, event_data.message.subject)) copy_msg["trigger"] = rule.name or ( rule.type.replace("_", " ") + " untitled") copy_msg["log"] = "%s\n%s" % (user_std_out.getvalue(), copy_msg["log"] if "log" in copy_msg else "") new_log[copy_msg["timestamp"]] = copy_msg # flush buffer user_std_out = StringIO() # set the stdout to a string sys.stdout = user_std_out # set the user logger to userLoggerStream = user_std_out mailbox.new_message_handler.removeAllHandles() mailbox.added_flag_handler.removeAllHandles() mailbox.deadline_handler.removeAllHandles() # Task manager for task in TaskManager.objects.filter( imap_account=mailbox._imap_account): now = timezone.now().replace(microsecond=0) is_fired = False logger.critical("%s %s" % (task.date, now)) if task.date > now: continue new_msg = {} new_msg["timestamp"] = str( datetime.datetime.now().strftime("%m/%d %H:%M:%S,%f")) new_msg["type"] = "see-later" copy_msg = copy.deepcopy(new_msg) copy_msg["timestamp"] = str( datetime.datetime.now().strftime("%m/%d %H:%M:%S,%f")) try: if new_msg["type"] == "see-later": user_environ = json.loads(task.email_rule.code) if len( task.email_rule.code) else {} msg_schema = MessageSchema.objects.get( base_message__id=user_environ["base_message_id"], folder__name=user_environ["hide_in"]) mailbox._imap_client.select_folder(user_environ["hide_in"]) msg = Message(msg_schema, mailbox._imap_client) msg.move(user_environ["current_folder"]) elif new_msg["type"] == "remind": user_environ = json.loads(task.email_rule.code) if len( task.email_rule.code) else {} for msg_schema in task.base_message.messages.all(): mailbox._imap_client.select_folder( user_environ["hide_in"]) msg = Message(msg_schema, mailbox._imap_client) msg.forward(user_environ["note"]) break else: # TODO replace with Task schema and make it more extensible # TODO task; id, type="hide-show", string='{}' pass is_fired = True except Exception as e: logger.critical("Error during task managing %s " % e) copy_msg["error"] = True exc_type, exc_obj, exc_tb = sys.exc_info() logger.info(e) logger.debug(exc_obj) # logger.info(traceback.print_exception()) # TODO find keyword 'in on_message' or on_flag_change logger.info(traceback.format_tb(exc_tb)) logger.info(sys.exc_info()) copy_msg["log"] = str(e) + traceback.format_tb(exc_tb)[-1] task.delete() finally: if is_fired: copy_msg["trigger"] = task.email_rule.name task.delete() # copy_msg["log"] = "%s\n%s" % (user_std_out.getvalue(), copy_msg["log"] ) # new_log[copy_msg["timestamp"]] = copy_msg # flush buffer user_std_out = StringIO() # set the stdout to a string sys.stdout = user_std_out # set the user logger to userLoggerStream = user_std_out except Exception as e: res['status'] = False logger.exception("failure running user %s code" % mailbox._imap_account.email) finally: # set the stdout back to what it was sys.stdout = sys.__stdout__ userLoggerStream = sys.__stdout__ # if it is simulate don't save to db if mailbox.is_simulate: logger.debug(res) # save logs to db else: # logger.info(new_log) res['imap_log'] = new_log user_std_out.close() return res