def _create_new_key(self, vcard, keytype): passphrase = okay_random(26, self.session.config.master_key ).lower() random_uid = vcard.random_uid bits = int(keytype.replace('RSA', '')) key_args = { # FIXME: EC keys! 'bits': bits, 'name': vcard.fn, 'email': vcard.email, 'passphrase': passphrase, 'comment': '' } event = Event(source=self, message=_('Generating new %d bit PGP key. ' 'This may take some time!') % bits, flags=Event.INCOMPLETE, data={'keygen_started': int(time.time()), 'profile_id': random_uid}, private_data=key_args) self._key_generator = GnuPGKeyGenerator( # FIXME: Passphrase handling is a problem here variables=dict_merge(GnuPGKeyGenerator.VARIABLES, key_args), on_complete=(random_uid, lambda: self._new_key_created(event, random_uid, passphrase)) ) self._key_generator.start() self.session.config.event_log.log_event(event)
class ProfileVCardCommand(parent): SYNOPSIS = tuple(synopsis) KIND = 'profile' ORDER = ('Tagging', 3) VCARD = "profile" DEFAULT_KEYTYPE = 'RSA2048' def _yn(self, val, default='no'): return (self.data.get(val, [default])[0][:2].lower() in ('ye', '1', 'on', 'tr')) def _sendmail_command(self): # FIXME - figure out where sendmail is for reals return mailpile.defaults.DEFAULT_SENDMAIL def _sanity_check(self, kind, triplets): route_id = self.data.get('route_id', [None])[0] if (route_id or [k for k in self.data.keys() if k[:5] in ('route', 'smtp-', 'sourc', 'secur', 'local')]): if len(triplets) > 1 or kind != 'profile': raise ValueError('Can only configure detailed settings ' 'for one profile at a time') # FIXME: Check more important invariants and raise def _configure_sending_route(self, vcard, route_id): # Sending route route = self.session.config.routes.get(route_id) protocol = self.data.get('route-protocol', ['none'])[0] if protocol == 'none': if route: del self.session.config.routes[route_id] vcard.route = '' return elif protocol == 'local': route.password = route.username = route.host = '' route.name = _("Local mail") route.command = self.data.get('route-command', [None] )[0] or self._sendmail_command() elif protocol in ('smtp', 'smtptls', 'smtpssl'): route.command = '' route.name = vcard.email for var in ('route-username', 'route-password', 'route-auth_type', 'route-host', 'route-port'): rvar = var.split('-', 1)[1] route[rvar] = self.data.get(var, [''])[0] else: raise ValueError(_('Unhandled outgoing mail protocol: %s' ) % protocol) route.protocol = protocol vcard.route = route_id def _get_mail_spool(self): path = os.getenv('MAIL') or None user = os.getenv('USER') if user and not path: if os.path.exists('/var/spool/mail'): path = os.path.normpath('/var/spool/mail/%s' % user) if os.path.exists('/var/mail'): path = os.path.normpath('/var/mail/%s' % user) return path def _configure_mail_sources(self, vcard): config = self.session.config sources = [r[7:].rsplit('-', 1)[0] for r in self.data.keys() if r.startswith('source-') and r.endswith('-protocol')] for src_id in sources: prefix = 'source-%s-' % src_id protocol = self.data.get(prefix + 'protocol', ['none'])[0] def configure_source(source): source.host = '' source.password = '' source.username = '' source.enabled = self._yn(prefix + 'enabled') source.discovery.create_tag = True source.discovery.process_new = True if src_id not in vcard.sources(): vcard.add_source(source._key) return source def make_new_source(): # This little dance makes sure source is actually a # config section, not just an anonymous dict. if src_id not in config.sources: config.sources[src_id] = {} source = config.sources[src_id] source.profile = vcard.random_uid source.discovery.apply_tags = [vcard.tag] return configure_source(source) if protocol == 'none': pass elif protocol == 'local': source = configure_source(vcard.get_source_by_proto( 'local', create=src_id)) elif protocol == 'spool': path = self._get_mail_spool() if not path: raise ValueError(_('Mail spool not found')) if path in config.sys.mailbox.values(): raise ValueError(_('Already configured: %s') % path) else: mailbox_idx = config.sys.mailbox.append(path) source = configure_source(vcard.get_source_by_proto( 'local', create=src_id)) src_id = source._key # We need to communicate with the source below, # so we save config to trigger instanciation. self._background_save(config=True, wait=True) inbox = [t._key for t in config.get_tags(type='inbox')] local_copy = self._yn(prefix + 'copy-local') if self._yn(prefix + 'delete-source'): policy = 'move' else: policy = 'read' src_obj = config.mail_sources[src_id] src_obj.take_over_mailbox(mailbox_idx, policy=policy, create_local=local_copy, apply_tags=inbox, save=False) elif protocol in ('imap', 'imap_ssl', 'imap_tls', 'pop3', 'pop3_ssl'): source = make_new_source() # Discovery policy disco = source.discovery if self._yn(prefix + 'index-all-mail'): if self._yn(prefix + 'leave-on-server'): disco.policy = 'sync' else: disco.policy = 'move' disco.local_copy = True disco.paths = [''] else: disco.policy = 'ignore' disco.local_copy = False disco.paths = [] disco.guess_tags = True disco.visible_tags = self._yn(prefix + 'visible-tags') # Connection settings for rvar in ('protocol', 'auth_type', 'host', 'port', 'username', 'password'): source[rvar] = self.data.get(prefix + rvar, [''])[0] if (self._yn(prefix + 'force-starttls') and source.protocol == 'imap'): source.protocol = 'imap_tls' username = source.username if '@' not in username: username += '@%s' % source.host source.name = username else: raise ValueError(_('Unhandled incoming mail protocol: %s' ) % protocol) def _new_key_created(self, event, vcard_rid, passphrase): config = self.session.config fingerprint = self._key_generator.generated_key if fingerprint: vcard = vcard_rid and config.vcards.get_vcard(vcard_rid) if vcard: vcard.pgp_key = fingerprint vcard.save() event.message = _('The PGP key for %s is ready for use.' ) % vcard.email else: event.message = _('PGP key generation is complete') # Record the passphrase! config.secrets[fingerprint] = {'password': passphrase} # FIXME: Toggle something that indicates we need a backup ASAP. self._background_save(config=True) else: event.message = _('PGP key generation failed!') event.data['keygen_failed'] = True event.flags = event.COMPLETE event.data['keygen_finished'] = int(time.time()) config.event_log.log_event(event) def _create_new_key(self, vcard, keytype): passphrase = okay_random(26, self.session.config.master_key ).lower() random_uid = vcard.random_uid bits = int(keytype.replace('RSA', '')) key_args = { # FIXME: EC keys! 'bits': bits, 'name': vcard.fn, 'email': vcard.email, 'passphrase': passphrase, 'comment': '' } event = Event(source=self, message=_('Generating new %d bit PGP key. ' 'This may take some time!') % bits, flags=Event.INCOMPLETE, data={'keygen_started': int(time.time()), 'profile_id': random_uid}, private_data=key_args) self._key_generator = GnuPGKeyGenerator( # FIXME: Passphrase handling is a problem here variables=dict_merge(GnuPGKeyGenerator.VARIABLES, key_args), on_complete=(random_uid, lambda: self._new_key_created(event, random_uid, passphrase)) ) self._key_generator.start() self.session.config.event_log.log_event(event) def _configure_security(self, vcard): openpgp_key = self.data.get('security-pgp-key', [''])[0] if openpgp_key: if openpgp_key.startswith('!CREATE'): key_type = openpgp_key[8:] or self.DEFAULT_KEYTYPE self._create_new_key(vcard, key_type) else: vcard.pgp_key = openpgp_key # FIXME: Schedule a background sync job which edits # the key to add this Account as a UID, if it else: vcard.remove_all('key') # Set the following even if we don't have a key, so they don't # get lost if the user edits settings while a key is being # generated - or if they just deselect a key temporarily. # Encryption policy rules outg_auto = self._yn('security-best-effort-crypto') outg_sig = self._yn('security-always-sign') outg_enc = self._yn('security-always-encrypt') if outg_enc and outg_sig: vcard.crypto_policy = 'openpgp-sign-encrypt' elif outg_sig: vcard.crypto_policy = 'openpgp-sign' elif outg_enc: vcard.crypto_policy = 'openpgp-encrypt' elif outg_auto: vcard.crypto_policy = 'best-effort' else: vcard.crypto_policy = 'none' # Crypto formatting rules pgp_keys = self._yn('security-attach-keys') pgp_inline = self._yn('security-prefer-inline') pgp_hdr_enc = self._yn('security-openpgp-header-encrypt') pgp_hdr_sig = self._yn('security-openpgp-header-sign') pgp_hdr_none = self._yn('security-openpgp-header-none') pgp_hdr_both = pgp_hdr_enc and pgp_hdr_sig if pgp_hdr_both: pgp_hdr_enc = pgp_hdr_sig = False vcard.crypto_format = ''.join([ 'openpgp_header:SE+' if (pgp_hdr_both) else '', 'openpgp_header:S+' if (pgp_hdr_sig) else '', 'openpgp_header:E+' if (pgp_hdr_enc) else '', 'openpgp_header:N+' if (pgp_hdr_none) else '', 'send_keys+' if (pgp_keys) else '', 'prefer_inline' if (pgp_inline) else 'pgpmime' ])
def setup_command(self, session): changed = authed = False results = { 'secret_keys': self.list_secret_keys(), } error_info = None if self.data.get('_method') == 'POST' or self._testing(): # 1st, are we choosing or creating a new key? choose_key = self.data.get('choose_key', [''])[0] if choose_key and not error_info: if (choose_key not in results['secret_keys'] and choose_key != '!CREATE'): error_info = (_('Invalid key'), { 'invalid_key': True, 'chosen_key': choose_key }) # 2nd, check authentication... # # FIXME: Creating a new key will allow a malicious actor to # bypass authentication and change settings. # try: passphrase = self.data.get('passphrase', [''])[0] passphrase2 = self.data.get('passphrase_confirm', [''])[0] chosen_key = ((not error_info) and choose_key ) or session.config.prefs.gpg_recipient if not error_info: assert(passphrase == passphrase2) if chosen_key == '!CREATE': assert(passphrase != '') sps = SecurePassphraseStorage(passphrase) elif chosen_key: sps = mailpile.auth.VerifyAndStorePassphrase( session.config, passphrase=passphrase, key=chosen_key) else: sps = mailpile.auth.VerifyAndStorePassphrase( session.config, passphrase=passphrase) if not chosen_key: choose_key = '!CREATE' results['updated_passphrase'] = True session.config.gnupg_passphrase.data = sps.data mailpile.auth.SetLoggedIn(self) except AssertionError: error_info = (_('Invalid passphrase'), { 'invalid_passphrase': True, 'chosen_key': session.config.prefs.gpg_recipient }) # 3rd, if necessary master key and/or GPG key with BLOCK_HTTPD_LOCK, Idle_HTTPD(): if choose_key and not error_info: session.config.prefs.gpg_recipient = choose_key # FIXME: This should probably only happen if the GPG # key was successfully created. self.make_master_key() changed = True with Setup.KEY_WORKER_LOCK: if ((not error_info) and (session.config.prefs.gpg_recipient == '!CREATE') and (Setup.KEY_CREATING_THREAD is None or Setup.KEY_CREATING_THREAD.failed)): gk = GnuPGKeyGenerator( sps=session.config.gnupg_passphrase, on_complete=('notify', lambda: self.gpg_key_ready(gk))) Setup.KEY_CREATING_THREAD = gk Setup.KEY_CREATING_THREAD.start() # Finally we update misc. settings for key in self.HTTP_POST_VARS.keys(): # FIXME: This should probably only happen if the GPG # key was successfully created. # Continue iff all is well... if error_info: break if key in (['choose_key', 'passphrase', 'passphrase_confirm'] + TestableWebbable.HTTP_POST_VARS.keys()): continue try: val = self.data.get(key, [''])[0] if val: session.config.prefs[key] = self.TRUTHY[val.lower()] changed = True except (ValueError, KeyError): error_info = (_('Invalid preference'), { 'invalid_setting': True, 'variable': key }) results.update({ 'creating_key': (Setup.KEY_CREATING_THREAD is not None and Setup.KEY_CREATING_THREAD.running), 'creating_failed': (Setup.KEY_CREATING_THREAD is not None and Setup.KEY_CREATING_THREAD.failed), 'chosen_key': session.config.prefs.gpg_recipient, 'prefs': { 'index_encrypted': session.config.prefs.index_encrypted, 'obfuscate_index': session.config.prefs.obfuscate_index, 'encrypt_mail': session.config.prefs.encrypt_mail, 'encrypt_index': session.config.prefs.encrypt_index, 'encrypt_vcards': session.config.prefs.encrypt_vcards, 'encrypt_events': session.config.prefs.encrypt_events, 'encrypt_misc': session.config.prefs.encrypt_misc } }) if changed: self._background_save(config=True) if error_info: return self._error(error_info[0], info=error_info[1], result=results) elif changed: return self._success(_('Updated crypto preferences'), results) else: return self._success(_('Configure crypto preferences'), results)
class ProfileVCardCommand(parent): SYNOPSIS = tuple(synopsis) KIND = 'profile' ORDER = ('Tagging', 3) VCARD = "profile" DEFAULT_KEYTYPE = 'RSA2048' def _yn(self, val, default='no'): return (self.data.get(val, [default])[0][:2].lower() in ('ye', '1', 'on', 'tr')) def _sendmail_command(self): # FIXME - figure out where sendmail is for reals return mailpile.defaults.DEFAULT_SENDMAIL def _sanity_check(self, kind, triplets): route_id = self.data.get('route_id', [None])[0] if (route_id or [k for k in self.data.keys() if k[:5] in ('route', 'smtp-', 'sourc', 'secur', 'local')]): if len(triplets) > 1 or kind != 'profile': raise ValueError('Can only configure detailed settings ' 'for one profile at a time') # FIXME: Check more important invariants and raise def _configure_sending_route(self, vcard, route_id): # Sending route route = self.session.config.routes.get(route_id) protocol = self.data.get('route-protocol', ['none'])[0] if protocol == 'none': if route: del self.session.config.routes[route_id] vcard.route = '' return elif protocol == 'local': route.password = route.username = route.host = '' route.name = _("Local mail") route.command = self.data.get('route-command', [None] )[0] or self._sendmail_command() elif protocol in ('smtp', 'smtptls', 'smtpssl'): route.command = '' route.name = vcard.email for var in ('route-username', 'route-password', 'route-auth_type', 'route-host', 'route-port'): rvar = var.split('-', 1)[1] route[rvar] = self.data.get(var, [''])[0] else: raise ValueError(_('Unhandled outgoing mail protocol: %s' ) % protocol) route.protocol = protocol vcard.route = route_id def _get_mail_spool(self): path = os.getenv('MAIL') or None user = os.getenv('USER') if user and not path: if os.path.exists('/var/mail'): path = '/var/mail/%s' % user if os.path.exists('/var/spool/mail'): path = '/var/spool/mail/%s' % user return path def _configure_mail_sources(self, vcard): config = self.session.config sources = [r[7:].rsplit('-', 1)[0] for r in self.data.keys() if r.startswith('source-') and r.endswith('-protocol')] for src_id in sources: prefix = 'source-%s-' % src_id protocol = self.data.get(prefix + 'protocol', ['none'])[0] def make_new_source(): # This little dance makes sure source is actually a # config section, not just an anonymous dict. if src_id not in config.sources: config.sources[src_id] = {} source = config.sources[src_id] source.host = '' source.password = '' source.username = '' source.profile = vcard.random_uid source.enabled = self._yn(prefix + 'enabled') source.discovery.apply_tags = [vcard.tag] source.discovery.create_tag = True source.discovery.process_new = True if src_id not in vcard.sources(): vcard.add_source(src_id) return source if protocol == 'none': pass elif protocol == 'spool': path = self._get_mail_spool() if not path: raise ValueError(_('Mail spool not found')) if path in config.sys.mailbox.values(): raise ValueError(_('Already configured: %s') % path) else: mailbox_idx = config.sys.mailbox.append(path) source = make_new_source() source.name = path source.protocol = 'mbox' for tag in config.get_tags(type='inbox'): source.discovery.apply_tags.append(tag._key) # We need to communicate with the source below, # so we save config to trigger instanciation. self._background_save(config=True, wait=True) local_copy = self._yn(prefix + 'copy-local') if self._yn(prefix + 'delete-source'): policy = 'move' else: policy = 'read' src_obj = config.mail_sources[src_id] src_obj.take_over_mailbox(mailbox_idx, policy=policy, create_local=local_copy, save=False) elif protocol in ('imap', 'imap_ssl', 'pop3', 'pop3_ssl'): source = make_new_source() # Discovery policy disco = source.discovery if self._yn(prefix + 'index-all-mail'): if self._yn(prefix + 'leave-on-server'): disco.policy = 'sync' else: disco.policy = 'move' disco.local_copy = True else: disco.policy = 'ignore' disco.local_copy = False disco.paths = [''] disco.guess_tags = True disco.visible_tags = self._yn(prefix + 'visible-tags') # Connection settings for rvar in ('protocol', 'auth_type', 'host', 'port', 'username', 'password'): source[rvar] = self.data.get(prefix + rvar, [''])[0] username = source.username if '@' not in username: username += '@%s' % source.host source.name = username else: raise ValueError(_('Unhandled incoming mail protocol: %s' ) % protocol) def _new_key_created(self, event, vcard_rid, passphrase): config = self.session.config fingerprint = self._key_generator.generated_key if fingerprint: vcard = vcard_rid and config.vcards.get_vcard(vcard_rid) if vcard: vcard.pgp_key = fingerprint vcard.save() event.message = _('The PGP key for %s is ready for use.' ) % vcard.email else: event.message = _('PGP key generation is complete') # Record the passphrase! config.secrets[fingerprint] = {'password': passphrase} # FIXME: Toggle something that indicates we need a backup ASAP. self._background_save(config=True) else: event.message = _('PGP key generation failed!') event.data['keygen_failed'] = True event.flags = event.COMPLETE event.data['keygen_finished'] = int(time.time()) config.event_log.log_event(event) def _create_new_key(self, vcard, keytype): passphrase = okay_random(26, self.session.config.master_key ).lower() random_uid = vcard.random_uid bits = int(keytype.replace('RSA', '')) key_args = { # FIXME: EC keys! 'bits': bits, 'name': vcard.fn, 'email': vcard.email, 'passphrase': passphrase, 'comment': '' } event = Event(source=self, message=_('Generating new %d bit PGP key. ' 'This may take some time!') % bits, flags=Event.INCOMPLETE, data={'keygen_started': int(time.time()), 'profile_id': random_uid}, private_data=key_args) self._key_generator = GnuPGKeyGenerator( # FIXME: Passphrase handling is a problem here variables=dict_merge(GnuPGKeyGenerator.VARIABLES, key_args), on_complete=(random_uid, lambda: self._new_key_created(event, random_uid, passphrase)) ) self._key_generator.start() self.session.config.event_log.log_event(event) def _configure_security(self, vcard): openpgp_key = self.data.get('security-pgp-key', [''])[0] if openpgp_key: if openpgp_key.startswith('!CREATE'): key_type = openpgp_key[8:] or self.DEFAULT_KEYTYPE self._create_new_key(vcard, key_type) else: vcard.pgp_key = openpgp_key # FIXME: Schedule a background sync job which edits # the key to add this Account as a UID, if it else: vcard.remove_all('key') # Set the following even if we don't have a key, so they don't # get lost if the user edits settings while a key is being # generated - or if they just deselect a key temporarily. # Encryption policy rules outg_auto = self._yn('security-best-effort-crypto') outg_sig = self._yn('security-always-sign') outg_enc = self._yn('security-always-encrypt') if outg_enc and outg_sig: vcard.crypto_policy = 'openpgp-sign-encrypt' elif outg_sig: vcard.crypto_policy = 'openpgp-sign' elif outg_enc: vcard.crypto_policy = 'openpgp-encrypt' elif outg_auto: vcard.crypto_policy = 'best-effort' else: vcard.crypto_policy = 'none' # Crypto formatting rules pgp_keys = self._yn('security-attach-keys') pgp_inline = self._yn('security-prefer-inline') pgp_hdr_enc = self._yn('security-openpgp-header-encrypt') pgp_hdr_sig = self._yn('security-openpgp-header-sign') pgp_hdr_none = self._yn('security-openpgp-header-none') pgp_hdr_both = pgp_hdr_enc and pgp_hdr_sig if pgp_hdr_both: pgp_hdr_enc = pgp_hdr_sig = False vcard.crypto_format = ''.join([ 'openpgp_header:SE+' if (pgp_hdr_both) else '', 'openpgp_header:S+' if (pgp_hdr_sig) else '', 'openpgp_header:E+' if (pgp_hdr_enc) else '', 'openpgp_header:N+' if (pgp_hdr_none) else '', 'send_keys+' if (pgp_keys) else '', 'prefer_inline' if (pgp_inline) else 'pgpmime' ])