conn_reject = [] # FIXME: reject ConnBroker.OUTGOING_TRACKABLE ? if url[:6].lower() == 'https:': conn_need = [ConnBroker.OUTGOING_HTTP] elif url[:5].lower() == 'http:': conn_need = [ConnBroker.OUTGOING_HTTPS] else: raise AccessError('Invalid URL scheme') try: with ConnBroker.context(need=conn_need, reject=conn_reject) as ctx: session.ui.mark('Getting: %s' % url) response = urlopen(url, data=None, timeout=timeout) except HTTPError, e: response = e data = response.read() headers = response.headers contenttype = headers.get('content-type', 'application/octet-stream') request = html_variables['http_request'] request.send_http_response(response.code, response.msg) request.send_standard_headers(mimetype=contenttype, header_list=[('Content-Length', len(data))]) request.wfile.write(data) raise SuppressHtmlOutput() _plugins.register_commands(JsApi, ProgressiveWebApp, HttpProxyGetRequest)
# Make everything in the background quit ASAP... mailpile.util.LAST_USER_ACTIVITY = 0 mailpile.util.QUITTING = mailpile.util.QUITTING or True if config.plugins: config.plugins.process_shutdown_hooks() config.stop_workers() if config.index: config.index.save_changes() if config.event_log: config.event_log.close() session.ui.display_result(Action(session, 'cleanup', '')) if session.interactive and config.sys.debug: session.ui.display_result(Action(session, 'ps', '')) # Remove anything that we couldn't remove before safe_remove() # Restart the app if that's what was requested if mailpile.util.QUITTING == 'restart': os.execv(sys.argv[0], sys.argv) _plugins.register_commands(InteractCommand, WaitCommand) if __name__ == "__main__": Main(sys.argv[1:])
if skip: continue results.append({ 'fid': fid, 'terms': trms, 'tags': tags, 'human_tags': ' '.join(human_tags), 'comment': cmnt, 'type': ftype }) return results class MoveFilter(ListFilters): """Move an auto-tagging rule""" SYNOPSIS = (None, 'filter/move', None, '<filter-id> <position>') ORDER = ('Tagging', 1) HTTP_CALLABLE = ('POST', 'UPDATE') COMMAND_SECURITY = security.CC_CHANGE_FILTERS def command(self): self.session.config.filter_move(self.args[0], self.args[1]) self._background_save(config=True) return ListFilters.command(self, want_fid=self.args[1]) _plugins.register_commands(Tag, TagLater, TagTemporarily, AddTag, DeleteTag, ListTags, Filter, DeleteFilter, MoveFilter, ListFilters)
if motd: self.event.data['motd'] = motd lang = config.prefs.language or 'en' motd['_motd'] = motd.get(lang, motd.get('en')) latest = motd.get('latest_version') if not latest: motd['_version_info'] = _('Mailpile update info unavailable') elif latest == APPVER: motd['_version_info'] = _('Your Mailpile is up to date') else: motd['_version_info'] = _('An upgrade for Mailpile is ' 'available, version %s' ) % latest if '--silent' in self.args: motd = {} elif '--ifnew' in self.args and not motd.get('_is_new'): motd = {} return self._success(message, result=motd) else: message = '%s: %s' % (_('Message Of The Day'), _('Unknown')) return self._error(message, result={}) _plugins.register_commands(MessageOfTheDay) _plugins.register_slow_periodic_job('motd', 3600, MessageOfTheDay.update)
session, handle = self.session, vcard.nickname return (Filter(session, arg=['delete', 'group:{0!s}'.format(handle)]).run() and DeleteTag(session, arg=[handle]).run()) return GroupVCardCommand class Group(GroupVCard(VCard)): """View groups""" class AddGroup(GroupVCard(AddVCard)): """Add groups""" class GroupAddLines(GroupVCard(VCardAddLines)): """Add lines to a group VCard""" class RemoveGroup(GroupVCard(RemoveVCard)): """Remove groups""" class ListGroups(GroupVCard(ListVCards)): """Find groups""" _plugins.register_commands(Group, AddGroup, GroupAddLines, RemoveGroup, ListGroups)
"""De-authenticate a user (log out)""" SYNOPSIS = (None, 'logout', 'auth/logout', '[<session ID>]') ORDER = ('Internals', 5) SPLIT_ARG = False IS_INTERACTIVE = True CONFIG_REQUIRED = False HTTP_AUTH_REQUIRED = False HTTP_CALLABLE = ('GET', 'POST') def command(self): # FIXME: Should this only be a POST request? # FIXME: This needs CSRF protection. session_id = self.session.ui.html_variables.get('http_session') if self.args and not session_id: session_id = self.args[0] if session_id: try: self.session.ui.debug('Logging out %s' % session_id) del SESSION_CACHE[session_id] return self._success(_('Goodbye!')) except KeyError: pass return self._error(_('No session found!')) plugin_manager = PluginManager(builtin=True) plugin_manager.register_commands(Authenticate, DeAuthenticate)
and (term not in cmnt.lower())): skip = True if skip: continue results.append({ 'fid': fid, 'terms': trms, 'tags': tags, 'human_tags': ' '.join(human_tags), 'comment': cmnt, 'type': ftype }) return results class MoveFilter(ListFilters): """Move an auto-tagging rule""" SYNOPSIS = (None, 'filter/move', None, '<filter-id> <position>') ORDER = ('Tagging', 1) HTTP_CALLABLE = ('POST', 'UPDATE') def command(self): self.session.config.filter_move(self.args[0], self.args[1]) self.session.config.save() return ListFilters.command(self, want_fid=self.args[1]) _plugins.register_commands(Tag, AddTag, DeleteTag, ListTags, Filter, DeleteFilter, MoveFilter, ListFilters)
if self.data.get('_method', None) == 'POST': password = self.data.get('password', [None])[0] else: password = self.session.ui.get_password( _('Enter your password:'******' ') if policy == 'store': if fingerprint in config.passphrases: del config.passphrases[fingerprint] config.secrets[fingerprint] = { 'password': password, 'policy': policy } return happy(_('Password stored permanently')) elif policy == 'cache-only' and password: sps = SecurePassphraseStorage(password) if ttl > 0: sps.expiration = time.time() + ttl config.passphrases[fingerprint] = sps if fingerprint.lower() in config.secrets: del config.secrets[fingerprint.lower()] return happy(_('Password stored temporarily')) else: return self._error(_('Invalid password policy!'), result) plugin_manager = PluginManager(builtin=True) plugin_manager.register_commands(Authenticate, DeAuthenticate, SetPassphrase)
results.update({ 'username': username, 'oauth_url': self.GetOAuthURL(session, ocfg, username) }) elif code: username, oname, csrf = state.split('/', 2) if not session.ui.valid_csrf_token(csrf): print 'Invalid CSRF token: %s' % csrf raise AccessError('Invalid CSRF token') oname, ocfg = self.GetOAuthConfig(config, oname=oname) tok_id, tok_info = self.GetToken(session, ocfg, code, tok_id=username) # This helps the mail sources/routes detect that it may # be worth trying the connection again... for msid, source in config.sources.iteritems(): if source.username == username: source.password = tok_info.access_token for msid, route in config.routes.iteritems(): if route.username == username: route.password = tok_info.access_token results['success'] = True return self._success(_('OAuth2 Authorization'), results) _ = gettext _plugins.register_commands(OAuth2)
# FIXME: Add route discovery mechanism. profile = { "email": uid["email"], "name": uid["name"], } session.config.profiles.append(profile) if not session.config.prefs.gpg_recipient: session.config.prefs.gpg_recipient = key session.ui.notify(_('Encrypting config to %s') % key) if session.config.prefs.crypto_policy == 'none': session.config.prefs.crypto_policy = 'openpgp-sign' else: session.ui.warning(_('Oh no, PGP/GPG support is unavailable!')) if (session.config.prefs.gpg_recipient and not (self._idx() and self._idx().INDEX) and not session.config.prefs.obfuscate_index): randcrap = sha512b64(open('/dev/urandom').read(1024), session.config.prefs.gpg_recipient, '%s' % time.time()) session.config.prefs.obfuscate_index = randcrap session.config.prefs.index_encrypted = True session.ui.notify(_('Obfuscating search index and enabling ' 'indexing of encrypted e-mail. ')) session.config.save() return self._success(_('Performed initial Mailpile setup')) _plugins.register_commands(Setup)
register(9500, AutoSmtpStartTLSConnBroker) register(9500, AutoImapStartTLSConnBroker) register(9500, AutoPop3StartTLSConnBroker) if socks is not None: register(1500, SocksConnBroker) register(3500, TorConnBroker) register(3500, TorRiskyBroker) register(3500, TorOnionBroker) # Note: At this point we have already imported security, which # also monkey-patches these same functions. This is a good # thing and is deliberate. :-) ssl.wrap_socket = monkey_patch(ssl.wrap_socket, SslWrapOnlyOnce) if hasattr(ssl, 'SSLContext'): ssl.SSLContext.wrap_socket = monkey_patch( ssl.SSLContext.wrap_socket, SslContextWrapOnlyOnce) from mailpile.plugins import PluginManager _plugins = PluginManager(builtin=__file__) _plugins.register_commands(NetworkHistory, GetTlsCertificate) else: import doctest import sys results = doctest.testmod(optionflags=doctest.ELLIPSIS, extraglobs={}) print '%s' % (results, ) if results.failed: sys.exit(1)
if len(self.args) > 2: server = self.args[2] n = Nicknym(self.session.config) return n.get_key(address, keytype, server) class NicknymRefreshKeys(Command): """Get a key from a nickserver""" ORDER = ('', 0) SYNOPSIS = (None, 'crypto/nicknym/refreshkeys', 'crypto/nicknym/refreshkeys', '') HTTP_CALLABLE = ('POST',) def command(self): n = Nicknym(self.session.config) n.refresh_keys() return True _plugins = PluginManager(builtin=__file__) _plugins.register_commands(NicknymGetKey) _plugins.register_commands(NicknymRefreshKeys) if __name__ == "__main__": n = Nicknym() print n.get_key("*****@*****.**")
motd = old_motd if motd: self.event.data['motd'] = motd lang = config.prefs.language or 'en' motd['_motd'] = motd.get(lang, motd.get('en')) latest = motd.get('latest_version') if not latest: motd['_version_info'] = _('Mailpile update info unavailable') elif latest == APPVER: motd['_version_info'] = _('Your Mailpile is up to date') else: motd['_version_info'] = _('An upgrade for Mailpile is ' 'available, version %s') % latest if '--silent' in self.args: motd = {} elif '--ifnew' in self.args and not motd.get('_is_new'): motd = {} return self._success(message, result=motd) else: message = '%s: %s' % (_('Message Of The Day'), _('Unknown')) return self._error(message, result={}) _plugins.register_commands(MessageOfTheDay) _plugins.register_slow_periodic_job('motd', 3600, MessageOfTheDay.update)
def command(self): session, config = self.session, self.session.config if 'q' in self.data: terms = [t.lower() for t in self.data['q']] else: terms = [t.lower() for t in self.args] count = int(self.data.get('count', 10)) offset = int(self.data.get('offset', 0)) vcard_addrs = self._vcard_addresses(config, terms) index_addrs = self._index_addresses(config, terms, vcard_addrs) addresses = vcard_addrs + index_addrs addresses.sort(key=lambda k: -k['rank']) total = len(addresses) return { 'addresses': addresses[offset:min(offset + count, total)], 'displayed': min(count, total), 'total': total, 'offset': offset, 'count': count, 'start': offset, 'end': offset + count, } _plugins.register_commands(VCard, AddVCard, VCardAddLines, RemoveVCard, ListVCards) _plugins.register_commands(Contact, AddContact, ContactAddLines, RemoveContact, ListContacts, AddressSearch) _plugins.register_commands(ContactImport, ContactImporters)
class ConnectToGuiOMatic(Command): """Connect to a waiting gui-o-matic GUI""" SYNOPSIS = (None, 'gui', 'gui', '[<secret>] [main|watch] <port>') ORDER = ('Internals', 9) CONFIG_REQUIRED = False IS_USER_ACTIVITY = False HTTP_CALLABLE = ('GET', 'POST') HTTP_AUTH_REQUIRED = False def command(self): if self.data.get('_method'): secret, style, port = self.args if secret != GetUserSecret(self.session.config): raise AccessError('Invalid User Secret') elif len(self.args) == 2: style, port = self.args elif len(self.args) == 1: style, port = 'main', self.args[0] with ConnBroker.context(need=[ConnBroker.OUTGOING_RAW]): guic = GuiOMaticConnection(self.session.config, socket.create_connection( ('localhost', int(port))), main=(style == 'main')) guic.start() return self._success("OK") _plugins.register_commands(ConnectToGuiOMatic)
if url[:6].lower() == 'https:': conn_need = [ConnBroker.OUTGOING_HTTP] elif url[:5].lower() == 'http:': conn_need = [ConnBroker.OUTGOING_HTTPS] else: raise AccessError('Invalid URL scheme') try: with ConnBroker.context(need=conn_need, reject=conn_reject) as ctx: session.ui.mark('Getting: %s' % url) response = urlopen(url, data=None, timeout=timeout) except HTTPError, e: response = e data = response.read() headers = response.headers contenttype = headers.get('content-type', 'application/octet-stream') request = html_variables['http_request'] request.send_http_response(response.code, response.msg) request.send_standard_headers(mimetype=contenttype, header_list=[('Content-Length', len(data))]) request.wfile.write(data) request.send_full_response(response.code, response.msg) raise SuppressHtmlOutput() _plugins.register_commands(JsApi, HttpProxyGetRequest)
def quit(self, join=True): self.quitting = True if join and self.isAlive(): self.join() class HashCash(Command): """Try to collide a hash using the SMTorP algorithm""" SYNOPSIS = (None, 'hashcash', None, '<bits> <challenge>') ORDER = ('Internals', 9) HTTP_CALLABLE = () COMMAND_SECURITY = security.CC_CPU_INTENSIVE def command(self): bits, challenge = int(self.args[0]), self.args[1] expected = 2 ** bits def marker(counter): progress = ((1024.0 * counter) / expected) * 100 self.session.ui.mark('Finding a %d-bit collision for %s (%d%%)' % (bits, challenge, progress)) collision = sha512_512kCollide(challenge, bits, callback1k=marker) return self._success({ 'challenge': challenge, 'collision': collision }) _plugins.register_worker(SMTPWorker) _plugins.register_commands(HashCash)
session, config = self.session, self.session.config if 'q' in self.data: terms = [t.lower() for t in self.data['q']] else: terms = [t.lower() for t in self.args] count = int(self.data.get('count', 10)) offset = int(self.data.get('offset', 0)) vcard_addrs = self._vcard_addresses(config, terms) index_addrs = self._index_addresses(config, terms, vcard_addrs) addresses = vcard_addrs + index_addrs addresses.sort(key=lambda k: -k['rank']) total = len(addresses) return { 'addresses': addresses[offset:min(offset+count, total)], 'displayed': min(count, total), 'total': total, 'offset': offset, 'count': count, 'start': offset, 'end': offset+count, } _plugins.register_commands(VCard, AddVCard, VCardAddLines, RemoveVCard, ListVCards) _plugins.register_commands(Contact, AddContact, ContactAddLines, RemoveContact, ListContacts, AddressSearch) _plugins.register_commands(ContactImport, ContactImporters)
for name, guard, step in cls._CHECKPOINTS(config): auth_required = (step.HTTP_AUTH_REQUIRED is True or (config.prefs.gpg_recipient and step.HTTP_AUTH_REQUIRED == 'Maybe')) if not guard(): if (not needed_auth) or (not auth_required): return step return default def setup_command(self, session): if '_method' in self.data: return self._success(_('Entering setup flow'), result=dict( ((c[0], c[1]() and True or False) for c in self._CHECKPOINTS(session.config) ))) else: return SetupMagic.setup_command(self, session) _ = gettext _plugins.register_commands(SetupMagic, SetupGetEmailSettings, SetupWelcome, SetupCrypto, SetupProfiles, SetupConfigureKey, SetupTestRoute, Setup)
class Watch(Command): """Watch the events fly by""" SYNOPSIS = (None, 'eventlog/watch', None, None) ORDER = ('Internals', 9) IS_USER_ACTIVITY = False CONFIG_REQUIRED = False def command(self): config = self.session.config unregister = False self.session.ui.notify( _('Watching logs: Press CTRL-C to return to the CLI')) try: while not mailpile.util.QUITTING and not config.event_log: time.sleep(1) unregister = config.event_log.ui_watch(self.session.ui) self.session.ui.unblock(force=True) while not mailpile.util.QUITTING: time.sleep(1) except KeyboardInterrupt: pass finally: if unregister: config.event_log.ui_unwatch(self.session.ui) return self._success(_('That was fun!')) _plugins.register_commands(Events, Cancel, Undo, Watch)
try: msg_info = idx.get_msg_at_idx_pos(msg_idx_pos) msg_emails = (idx.expand_to_list(msg_info, field=idx.MSG_TO) + idx.expand_to_list(msg_info, field=idx.MSG_CC)) emails.extend(msg_emails) if 'no_from' not in self.data: emails.append(msg_info[idx.MSG_FROM]) except ValueError: pass addrs = [ ai for ee in emails for ai in AddressHeaderParser(unicode_data=ee) ] return self._success(_('Choosing from address'), result={ 'emails': addrs, 'from': vcards.choose_from_address( self.session.config, addrs) }) _plugins.register_commands(VCard, AddVCard, RemoveVCard, ListVCards, VCardAddLines, VCardRemoveLines) _plugins.register_commands(Contact, AddContact, RemoveContact, ListContacts, AddressSearch) _plugins.register_commands(Profile, AddProfile, RemoveProfile, ListProfiles, ChooseFromAddress) _plugins.register_commands(ContactImport, ContactImporters)
class ConnectToGuiOMatic(Command): """Connect to a waiting gui-o-matic GUI""" SYNOPSIS = (None, 'gui', 'gui', '[<secret>] [main|watch] <port>') ORDER = ('Internals', 9) CONFIG_REQUIRED = False IS_USER_ACTIVITY = False HTTP_CALLABLE = ('GET', 'POST') HTTP_AUTH_REQUIRED = False def command(self): if self.data.get('_method'): secret, style, port = self.args if secret != GetUserSecret(self.session.config): raise AccessError('Invalid User Secret') elif len(self.args) == 2: style, port = self.args elif len(self.args) == 1: style, port = 'main', self.args[0] with ConnBroker.context(need=[ConnBroker.OUTGOING_RAW]): guic = GuiOMaticConnection( self.session.config, socket.create_connection(('localhost', int(port))), main=(style == 'main')) guic.start() return self._success("OK") _plugins.register_commands(ConnectToGuiOMatic)
class Cached(Command): """Fetch results from the command cache.""" SYNOPSIS = (None, 'cached', 'cached', '[<cache-id>]') ORDER = ('Internals', 7) HTTP_QUERY_VARS = {'id': 'Cache ID of command to redisplay'} IS_USER_ACTIVITY = False LOG_NOTHING = True def max_age(self): # Allow result to be cached by the browser for 2 seconds; we do # this to facilitate cross-tab sharing of cache results. return 2 # Warning: This depends on internals of Command, how things are run there. def run(self): try: cid = self.args[0] if self.args else self.data.get('id', [None])[0] rv = self.session.config.command_cache.get_result(cid) self.session.copy(rv.session) rv.session.ui.render_mode = self.session.ui.render_mode return rv except: self._starting() self._error(self.FAILURE % {'name': self.name, 'args': ' '.join(self.args)}) return self._finishing(False) _plugins.register_commands(Cached)
for tag in cfg.get_tags(type='outbox'): search = ['in:%s' % tag._key] for msg_idx_pos in idx.search(self.session, search, order='flat-index').as_set(): messages.append('=%s' % b36(msg_idx_pos)) if messages: self.args = tuple(messages) return Sendit.command(self) else: return self._success(_('The outbox is empty')) _plugins.register_config_variables('prefs', { 'empty_outbox_interval': [_('Delay between attempts to send mail'), int, 90] }) _plugins.register_slow_periodic_job('sendmail', 'prefs.empty_outbox_interval', EmptyOutbox.sendmail) _plugins.register_commands( Compose, Reply, Forward, # Create Draft, Update, Attach, # Manipulate UnThread, # ... Sendit, UpdateAndSendit, # Send EmptyOutbox) # ...
for i in reversed(range(0, len(msg_idxs))): mi = msg_idxs[i] msg_idxs[i:i + 1] = [ int(m[idx.MSG_MID], 36) for m in idx.get_conversation(msg_idx=mi) ] # Let's always export in the same order. Stability is nice. msg_idxs.sort() mbox = self.create_mailbox(mbox_type, path) exported = {} while msg_idxs: msg_idx = msg_idxs.pop(0) if msg_idx not in exported: e = Email(idx, msg_idx) session.ui.mark('Exporting =%s ...' % e.msg_mid()) mbox.add(e.get_msg()) exported[msg_idx] = 1 mbox.flush() return self._success( _('Exported %d messages to %s') % (len(exported), path), { 'exported': len(exported), 'created': path }) _plugins.register_commands(ExportMail)
cids = [cid] for c in cids: fn, info = e.extract_attachment(session, c, name_fmt=name_fmt, mode=mode) if info: info['idx'] = e.msg_idx_pos if fn: info['created_file'] = fn results.append(info) return results _plugins.register_commands(Extract, Next, Order, Previous, Search, View) ##[ Search terms ]############################################################ def mailbox_search(config, idx, term, hits): word = term.split(':', 1)[1].lower() try: mbox_id = FormatMbxId(b36(int(word, 36))) except ValueError: mbox_id = None mailboxes = [] for m in config.sys.mailbox.keys(): fn = FilePath(config.sys.mailbox[m]).display().lower() if (mbox_id == m) or word in fn:
event.flags = event.COMPLETE self.session.config.event_log.log_event(event) canceled.append(event.event_id) return self._success(_('Canceled %d events') % len(canceled), canceled) class Undo(Command): """Undo an event""" SYNOPSIS = (None, 'eventlog/undo', 'eventlog/undo', '<eventID>') ORDER = ('Internals', 9) HTTP_CALLABLE = ('POST', ) HTTP_POST_VARS = { 'event_id': 'Event ID' } IS_USER_ACTIVITY = False def command(self): event_id = self.data.get('event_id', [None])[0] or self.args[0] event = self.session.config.event_log.get(event_id) if event: try: return event.source_class.Undo(self, event) except (NameError, AttributeError): return self._error(_('Event is not undoable')) else: return self._error(_('Event not found')) _plugins.register_commands(Events, Cancel, Undo)
'address': 'The nick/address to find a key for', 'allowremote': 'Whether to permit remote key lookups (defaults to true)' } def command(self): if len(self.args) > 1: allowremote = self.args.pop() else: allowremote = self.data.get('allowremote', True) address = " ".join(self.data.get('address', self.args)) return lookup_crypto_keys(self.session, address, event=self.event, allowremote=allowremote) _plugins = PluginManager(builtin=__file__) _plugins.register_commands(KeyLookup) class LookupHandler: NAME = "NONE" LOCAL = False def __init__(self, session): self.session = session def _score(self, key): raise NotImplemented("Subclass and override _score") def _lookup(self, address): raise NotImplemented("Subclass and override _lookup")
HTTP_QUERY_VARS = { 'q': 'selectors', 'no_public': 'omit public keys', 'no_secret': 'omit secret keys' } COMMAND_SECURITY = security.CC_CHANGE_CONTACTS def command(self): session, config = self.session, self.session.config selectors = [a for a in self.args if not a.startswith('-')] selectors.extend(self.data.get('q', [])) public = not (int(self.data.get('no_public', [0])[0]) or '-no_public' in self.args) secret = not (int(self.data.get('no_secret', [0])[0]) or '-no_secret' in self.args) imported = 0 for cfg in config.prefs.vcard.importers.gpg: gimp = GnuPGImporter(session, cfg) imported += gimp.import_vcards(session, config.vcards, selectors=selectors) return self._success(_('Imported %d vCards from GPG keychain' ) % imported, {'vcards': imported}) _plugins.register_commands(PGPKeysAsVCards, PGPKeysImportAsVCards) _plugins.register_vcard_importers(GnuPGImporter)
if a['filename'].lower().endswith(cid[1:].lower())] else: cids = [cid] for c in cids: fn, info = e.extract_attachment(session, c, name_fmt=name_fmt, mode=mode) if info: info['idx'] = e.msg_idx_pos if fn: info['created_file'] = fn results.append(info) return results _plugins.register_commands(Extract, Next, Order, Previous, Search, View) ##[ Search terms ]############################################################ def mailbox_search(config, idx, term, hits): word = term.split(':', 1)[1].lower() try: mbox_id = (('0' * MBX_ID_LEN) + b36(int(word, 36)))[-MBX_ID_LEN:] except ValueError: mbox_id = None mailboxes = [m for m in config.sys.mailbox.keys() if (mbox_id == m) or word in config.sys.mailbox[m].lower()] rt = [] for mbox_id in mailboxes:
html = markdown(str(self.result['urlmap'])) except: import traceback print traceback.format_exc() html = '<pre>%s</pre>' % escape_html(self.result['urlmap']) self.result['markdown'] = html return Command.CommandResult.as_html(self, *args, **kwargs) def command(self): prefix = self.args[0] if self.args else None return {'urlmap': UrlMap(self.session).map_as_markdown(prefix=prefix)} plugin_manager = PluginManager(builtin=True) if __name__ != "__main__": plugin_manager.register_commands(HelpUrlMap, UrlRedirect, UrlRedirectEdit, UrlRedirectThread) else: # If run as a python script, print map and run doctests. import doctest import sys import mailpile.app import mailpile.config.defaults as defaults import mailpile.config.manager as cfg_manager import mailpile.plugins import mailpile.ui # Import all the default plugins from mailpile.plugins import * rules = defaults.CONFIG_RULES
skip = True if skip: continue results.append({ 'fid': fid, 'terms': trms, 'tags': tags, 'human_tags': ' '.join(human_tags), 'comment': cmnt, 'type': ftype }) return results class MoveFilter(ListFilters): """Move an auto-tagging rule""" SYNOPSIS = (None, 'filter/move', None, '<filter-id> <position>') ORDER = ('Tagging', 1) HTTP_CALLABLE = ('POST', 'UPDATE') def command(self): self.session.config.filter_move(self.args[0], self.args[1]) self.session.config.save() return ListFilters.command(self, want_fid=self.args[1]) _plugins.register_commands(Tag, AddTag, DeleteTag, ListTags, Filter, DeleteFilter, MoveFilter, ListFilters)
else: tag[at_config.match_tag].append(mid) elif at_config.unsure_tag and want is None: if at_config.unsure_tag not in tag: tag[at_config.unsure_tag] = [mid] else: tag[at_config.unsure_tag].append(mid) for tid in tag: idx.add_tag(session, tid, msg_idxs=[int(i, 36) for i in tag[tid]]) return self._success(_('Auto-tagged %d messages') % len(emails), tag) _plugins.register_commands(Retrain, Classify, AutoTag) ##[ Keywords ]################################################################ def filter_hook(session, msg_mid, msg, keywords, **kwargs): """Classify this message.""" if not kwargs.get('incoming', False): return keywords config = session.config for at_config in autotag_configs(config): try: at_tag = config.get_tag(at_config.match_tag) atagger = config.load_auto_tagger(at_config) if not atagger.trained:
session, handle = self.session, vcard.nickname return (Filter(session, arg=['delete', 'group:%s' % handle]).run() and DeleteTag(session, arg=[handle]).run()) return GroupVCardCommand class Group(GroupVCard(VCard)): """View groups""" class AddGroup(GroupVCard(AddVCard)): """Add groups""" class GroupAddLines(GroupVCard(VCardAddLines)): """Add lines to a group VCard""" class RemoveGroup(GroupVCard(RemoveVCard)): """Remove groups""" class ListGroups(GroupVCard(ListVCards)): """Find groups""" _plugins.register_commands(Group, AddGroup, GroupAddLines, RemoveGroup, ListGroups)
if (rcpt != sender) and rcpt_keyid: kb = gnupg.get_minimal_key(key_id=rcpt_keyid, user_id=rcpt) if kb: gossip_list.append( make_autocrypt_header( rcpt, kb, prefix='Autocrypt-Gossip')) except (ValueError, IndexError): pass if len(gossip_list) > 1: # No point gossiping peoples keys back to them alone. for hdr in gossip_list: msg.add_header('Autocrypt-Gossip', hdr) matched = True return sender, rcpts, msg, matched, True _plugins.register_meta_kw_extractor('autocrypt', autocrypt_meta_kwe) _plugins.register_commands(AutoCryptSearch, AutoCryptForget, AutoCryptParse, AutoCryptPeers) # Note: we perform our transformations BEFORE the GnuPG transformations # (prio 500), so the memory hole transformation can take care of hiding # the Autocrypt-Gossip headers. _plugins.register_outgoing_email_content_transform('400_autocrypt', AutoCryptTxf)
# sense. if not flat: for i in reversed(range(0, len(msg_idxs))): mi = msg_idxs[i] msg_idxs[i:i+1] = [int(m[idx.MSG_MID], 36) for m in idx.get_conversation(msg_idx=mi)] # Let's always export in the same order. Stability is nice. msg_idxs.sort() mbox = self.create_mailbox(mbox_type, path) exported = {} while msg_idxs: msg_idx = msg_idxs.pop(0) if msg_idx not in exported: e = Email(idx, msg_idx) session.ui.mark('Exporting =%s ...' % e.msg_mid()) mbox.add(e.get_msg()) exported[msg_idx] = 1 mbox.flush() return self._success( _('Exported %d messages to %s') % (len(exported), path), { 'exported': len(exported), 'created': path }) _plugins.register_commands(ExportMail)
migrations = [] for a in self.args: if a in MIGRATIONS: migrations.append(MIGRATIONS[a]) else: raise UsageError(_('Unknown migration: %s (available: %s)' ) % (a, ', '.join(MIGRATIONS.keys()))) if not migrations: migrations = ((before_setup and MIGRATIONS_BEFORE_SETUP or []) + (after_setup and MIGRATIONS_AFTER_SETUP or [])) for mig in migrations: try: if mig(session): cnt += 1 else: err += 1 except: self._ignore_exception() err += 1 self.session.config.version = APPVER # We've migrated to this! self._background_save(config=True) return self._success(_('Performed %d migrations, failed %d.' ) % (cnt, err)) _plugins.register_commands(Migrate)
else: password = self.session.ui.get_password(_('Enter your password:'******' ') if policy == 'store': if fingerprint in config.passphrases: del config.passphrases[fingerprint] config.secrets[fingerprint] = { 'password': password, 'policy': policy } return happy(_('Password stored permanently')) elif policy == 'cache-only' and password: from mailpile.config import SecurePassphraseStorage sps = SecurePassphraseStorage(password) sps.expiration = time.time() + float(ttl) config.passphrases[fingerprint] = sps if fingerprint.lower() in config.secrets: del config.secrets[fingerprint.lower()] return happy(_('Password stored temporarily')) else: return self._error(_('Invalid password policy!'), result) plugin_manager = PluginManager(builtin=True) plugin_manager.register_commands(Authenticate, DeAuthenticate, SetPassphrase)
if 'send_keys' in cformat: send_keys = cls.ShouldAttachKey( config, vcards=[p[0] for p in policies], emails=[p[2] for p in policies if not p[0]]) else: send_keys = False return { 'reason': reason, 'can-sign': can_sign, 'can-encrypt': can_encrypt, 'crypto-policy': policy, 'crypto-format': cformat, 'send-keys': send_keys, 'addresses': dict([(e, AddressInfo(e, vc.fn if vc else e, vcard=vc)) for vc, k, e, p, f in policies if vc]) } def command(self): emails = list(self.args) + self.data.get('email', []) if len(emails) < 1: return self._error('Please provide at least one email address!') result = self.crypto_policy(self.session, self._idx(), emails) return self._success(result['reason'], result=result) _plugins.register_commands(CryptoPolicy, UpdateCryptoPolicyForUser)
except KeyboardInterrupt: pass finally: if readline: readline.write_history_file(session.config.history_file()) # Make everything in the background quit ASAP... mailpile.util.LAST_USER_ACTIVITY = 0 mailpile.util.QUITTING = True if config.plugins: config.plugins.process_shutdown_hooks() if config.loaded_config: config.flush_mbox_cache(session, wait=True) config.stop_workers() if config.index: config.index.save_changes() if config.event_log: config.event_log.close() if session.interactive and config.sys.debug: session.ui.display_result(Action(session, 'ps', '')) _plugins.register_commands(InteractCommand, WaitCommand) if __name__ == "__main__": Main(sys.argv[1:])
sock = org_sslwrap(sock, *args, **kwargs) Master.get_fd_context( sock.fileno()).encryption = _explain_encryption(sock) return sock ssl.wrap_socket = SslWrapOnlyOnce if have_ssl_context: # Same again with SSLContext, if we have it. def SslContextWrapOnlyOnce(self, sock, *args, **kwargs): if not isinstance(sock, ssl.SSLSocket): sock = org_context_wrap_socket(self, sock, *args, **kwargs) Master.get_fd_context( sock.fileno()).encryption = _explain_encryption(sock) return sock ssl.SSLContext.wrap_socket = SslContextWrapOnlyOnce from mailpile.plugins import PluginManager _plugins = PluginManager(builtin=__file__) _plugins.register_commands(NetworkHistory) else: import doctest import sys results = doctest.testmod(optionflags=doctest.ELLIPSIS, extraglobs={}) print '%s' % (results, ) if results.failed: sys.exit(1)
def command(self): if len(self.args) > 1: allowremote = self.args.pop() else: allowremote = self.data.get('allowremote', True) address = " ".join(self.data.get('address', self.args)) return lookup_crypto_keys(self.session, address, event=self.event, allowremote=allowremote) _plugins = PluginManager(builtin=__file__) _plugins.register_commands(KeyLookup) class LookupHandler: NAME = "NONE" LOCAL = False def __init__(self, session): self.session = session def _score(self, key): raise NotImplemented("Subclass and override _score") def _lookup(self, address): raise NotImplemented("Subclass and override _lookup")
return self._success(_('Loaded plugins: %s') % ', '.join(self.args), {'loaded': self.args}) class DisablePlugin(mailpile.commands.Command): """Disable a plugin.""" SYNOPSIS = (None, 'plugins/disable', 'plugins/disable', '<plugin>') ORDER = ('Config', 9) def command(self): config = self.session.config plugins = config.plugins for plugin in self.args: if plugin in plugins.REQUIRED: return self._error(_('Required plugins can not be disabled: %s' ) % plugin) if plugin not in config.sys.plugins: return self._error(_('Plugin not loaded: %s') % plugin) for plugin in self.args: while plugin in config.sys.plugins: config.sys.plugins.remove(plugin) self._serialize('Save config', lambda: config.save()) return self._success(_('Disabled plugins: %s (restart required)' ) % ', '.join(self.args), {'disabled': self.args}) _plugins.register_commands(Plugins, LoadPlugin, DisablePlugin)
if skip: continue results.append({ 'fid': fid, 'terms': trms, 'tags': tags, 'human_tags': ' '.join(human_tags), 'comment': cmnt, 'type': ftype }) return results class MoveFilter(ListFilters): """Move an auto-tagging rule""" SYNOPSIS = (None, 'filter/move', None, '<filter-id> <position>') ORDER = ('Tagging', 1) HTTP_CALLABLE = ('POST', 'UPDATE') def command(self): self.session.config.filter_move(self.args[0], self.args[1]) self._background_save(config=True) return ListFilters.command(self, want_fid=self.args[1]) _plugins.register_commands(Tag, TagLater, TagTemporarily, AddTag, DeleteTag, ListTags, Filter, DeleteFilter, MoveFilter, ListFilters)
session.config.prefs.crypto_policy = 'openpgp-sign' if len(accepted_keys) == 0: # FIXME: Start background process generating a key once a user # has supplied a name and e-mail address. pass else: session.ui.warning(_('Oh no, PGP/GPG support is unavailable!')) if (session.config.prefs.gpg_recipient and not (self._idx() and self._idx().INDEX) and not session.config.prefs.obfuscate_index): randcrap = sha512b64( open('/dev/urandom').read(1024), session.config.prefs.gpg_recipient, '%s' % time.time()) session.config.prefs.obfuscate_index = randcrap session.config.prefs.index_encrypted = True session.ui.notify( _('Obfuscating search index and enabling ' 'indexing of encrypted e-mail. ')) # Perform any required migrations Migrate(session).run(before_setup=False, after_setup=True) session.config.save() return self._success(_('Performed initial Mailpile setup')) _plugins.register_commands(Setup)
session, idx, _, _ = self._do_search(search=["from:%s" % addr, "has:pgp"]) pgp = 0 for messageid in session.results: pgp += 1 if total > 0: ratio = float(pgp)/total else: ratio = 0 res = {"messages": total, "pgpsigned": pgp, "ratio": ratio, "address": addr} return self._success("Got statistics for address", res) _plugins.register_commands(GPGKeySearch) _plugins.register_commands(GPGKeyReceive) _plugins.register_commands(GPGKeyImport) _plugins.register_commands(GPGKeyImportFromMail) _plugins.register_commands(GPGKeySign) _plugins.register_commands(GPGKeyList) _plugins.register_commands(GPGUsageStatistics) _plugins.register_commands(GPGKeyListSecret)
migrations = [] for a in self.args: if a in MIGRATIONS: migrations.append(MIGRATIONS[a]) else: raise UsageError( _('Unknown migration: %s (available: %s)') % (a, ', '.join(MIGRATIONS.keys()))) if not migrations: migrations = ((before_setup and MIGRATIONS_BEFORE_SETUP or []) + (after_setup and MIGRATIONS_AFTER_SETUP or [])) for mig in migrations: try: if mig(session): cnt += 1 else: err += 1 except: self._ignore_exception() err += 1 if cnt: session.config.save() return self._success( _('Performed %d migrations, failed %d.') % (cnt, err)) _plugins.register_commands(Migrate)
# Messages no longer in the outbox get their events canceled... if cfg.event_log: events = cfg.event_log.incomplete(source='.plugins.compose.Sendit') for ev in events: if ('mid' in ev.data and ('=%s' % ev.data['mid']) not in messages): ev.flags = ev.COMPLETE ev.message = _('Sending cancelled.') cfg.event_log.log_event(ev) # Send all the mail! if messages: self.args = tuple(set(messages)) return Sendit.command(self) else: return self._success(_('The outbox is empty')) _plugins.register_config_variables('prefs', { 'empty_outbox_interval': [_('Delay between attempts to send mail'), int, 10] }) _plugins.register_fast_periodic_job('sendmail', 'prefs.empty_outbox_interval', EmptyOutbox.sendmail) _plugins.register_commands(Compose, Reply, Forward, # Create Draft, Update, Attach, UnAttach, # Manipulate UnThread, # ... Sendit, UpdateAndSendit, # Send EmptyOutbox) # ...
serious += 1 if len(good_keys) == 0: fixes[:0] = [ self._fix_gen_key(min_bits=self.MIN_KEYSIZE), self._fix_mp_config() ] if quiet and not serious: return self._success('OK') ret = self._error if serious else self._success return ret(_('Sanity checked: %d keys in GPG keyring, %d profiles') % (len(secret_keys), len(profiles)), result={ 'passed': not serious, 'details': details, 'fixes': fixes }) _plugins.register_commands(GPGKeySearch) _plugins.register_commands(GPGKeyReceive) _plugins.register_commands(GPGKeyImport) _plugins.register_commands(GPGKeyImportFromMail) _plugins.register_commands(GPGKeySign) _plugins.register_commands(GPGKeyList) _plugins.register_commands(GPGUsageStatistics) _plugins.register_commands(GPGKeyListSecret) _plugins.register_commands(GPGCheckKeys)
[m for m in self.args if '@' not in m] + ['=%s' % mid for mid in self.data.get('mid', [])] ) for msg_idx_pos in messages: try: msg_info = idx.get_msg_at_idx_pos(msg_idx_pos) msg_emails = (idx.expand_to_list(msg_info, field=idx.MSG_TO) + idx.expand_to_list(msg_info, field=idx.MSG_CC)) emails.extend(msg_emails) if 'no_from' not in self.data: emails.append(msg_info[idx.MSG_FROM]) except ValueError: pass addrs = [ai for ee in emails for ai in AddressHeaderParser(unicode_data=ee)] return self._success(_('Choosing from address'), result={ 'emails': addrs, 'from': vcards.choose_from_address(self.session.config, addrs) }) _plugins.register_commands(VCard, AddVCard, RemoveVCard, ListVCards, VCardAddLines, VCardRemoveLines) _plugins.register_commands(Contact, AddContact, RemoveContact, ListContacts, AddressSearch) _plugins.register_commands(Profile, AddProfile, RemoveProfile, ListProfiles, ChooseFromAddress) _plugins.register_commands(ContactImport, ContactImporters)
config.sys.gpg_home = os_gpg_home config.sys.gpg_binary = os_gpg_binary config.sys.http_port = os_http_port config.sys.minfree_mb = os_minfree_mb self._restore_PGP_keys( config, backup_zip, self.data.get('keychain', ['shared'])[0]) self._adjust_paths(config) config.prepare_workers(session, daemons=True) message = _('Backup restored') results['restored'] = True AVAILABLE_BACKUPS = {} else: message = _('Backup validated, restoration is possible') AVAILABLE_BACKUPS[backup_date] = backup_data except (ValueError, KeyError, zipfile.BadZipfile, IOError): traceback.print_exc() return self._error('Incomplete, invalid or corrupt backup') else: message = _('Restore from backup') results['available'] = AVAILABLE_BACKUPS.keys() return self._success(message, result=results) _plugins.register_commands(MakeBackup, RestoreBackup)
'imap_port': 993, 'imap_tls': True, 'pop3_host': 'pop3.wigglebonk.com', 'pop3_port': 110, 'pop3_tls': False, 'smtp_host': 'smtp.wigglebonk.com', 'smtp_port': 465, 'smtp_tls': False } def _get_domain_settings(self, domain): raise Exception('FIXME') def setup_command(self, session): results = {} for email in list(self.args) + self.data.get('email'): settings = self._testing_data(self._get_domain_settings, self.TEST_DATA, email) if settings: results[email] = settings if results: self._success(_('Found settings for %d addresses'), results) else: self._error(_('No settings found')) _plugins.register_commands(Setup, SetupCheckKeychain, SetupCreateNewKey, SetupGuessEmails, SetupTestEmailSettings, Setup)
self.quitting = True if join: try: self.join() except RuntimeError: pass class HashCash(Command): """Try to collide a hash using the SMTorP algorithm""" SYNOPSIS = (None, 'hashcash', None, '<bits> <challenge>') ORDER = ('Internals', 9) HTTP_CALLABLE = () COMMAND_SECURITY = security.CC_CPU_INTENSIVE def command(self): bits, challenge = int(self.args[0]), self.args[1] expected = 2 ** bits def marker(counter): progress = ((1024.0 * counter) / expected) * 100 self.session.ui.mark('Finding a {0:d}-bit collision for {1!s} ({2:d}%)'.format(bits, challenge, progress)) collision = sha512_512kCollide(challenge, bits, callback1k=marker) return self._success({ 'challenge': challenge, 'collision': collision }) _plugins.register_worker(SMTPWorker) _plugins.register_commands(HashCash)
from markdown import markdown html = markdown(str(self.result['urlmap'])) except: import traceback print traceback.format_exc() html = '<pre>%s</pre>' % escape_html(self.result['urlmap']) self.result['markdown'] = html return Command.CommandResult.as_html(self, *args, **kwargs) def command(self): return {'urlmap': UrlMap(self.session).map_as_markdown()} plugin_manager = PluginManager(builtin=True) if __name__ != "__main__": plugin_manager.register_commands(HelpUrlMap, UrlRedirect, UrlRedirectEdit, UrlRedirectThread) else: # If run as a python script, print map and run doctests. import doctest import sys import mailpile.app import mailpile.config import mailpile.plugins.tags import mailpile.plugins.search import mailpile.plugins.compose import mailpile.plugins.contacts import mailpile.defaults import mailpile.plugins import mailpile.ui