def start(self, restart=False): """ Launch the firewall using the privileged wrapper. :returns: True if the exitcode of calling the root helper in a subprocess is 0. :rtype: bool """ gateways = [gateway for gateway, port in self._remotes] # XXX check for wrapper existence, check it's root owned etc. # XXX check that the iptables rules are in place. cmd = ["pkexec", self.BITMASK_ROOT, "firewall", "start"] if restart: cmd.append("restart") # FIXME -- use a processprotocol exitCode = subprocess.call(cmd + gateways) emit_async(catalog.VPN_STATUS_CHANGED) if exitCode == 0: return True else: return False
def _route_msg(self, encrypt_and_sign_result, raw): """ Sends the msg using the ESMTPSenderFactory. :param encrypt_and_sign_result: A tuple containing the 'maybe' encrypted message and the recipient :type encrypt_and_sign_result: tuple """ message, recipient = encrypt_and_sign_result self.log.info( 'Connecting to SMTP server %s:%s' % (self._host, self._port)) msg = message.as_string(False) # we construct a defer to pass to the ESMTPSenderFactory d = defer.Deferred() d.addCallback(self.sendSuccess) d.addErrback(self.sendError, raw) # we don't pass an ssl context factory to the ESMTPSenderFactory # because ssl will be handled by reactor.connectSSL() below. factory = smtp.ESMTPSenderFactory( "", # username is blank, no client auth here "", # password is blank, no client auth here self._from_address, recipient.dest.addrstr, StringIO(msg), d, heloFallback=True, requireAuthentication=False, requireTransportSecurity=True) factory.domain = bytes('leap.bitmask.mail-' + __version__) emit_async(catalog.SMTP_SEND_MESSAGE_START, self._from_address, recipient.dest.addrstr) reactor.connectSSL( self._host, self._port, factory, contextFactory=SSLContextFactory(self._cert, self._key))
def push_status(self): try: statusdict = self.do_status() status = statusdict.get('status').upper() emit_async(catalog.VPN_STATUS_CHANGED, status) except ValueError: pass
def __cb_signal_unread_to_ui(self, unseen): """ Send the unread signal to UI. :param unseen: number of unseen messages. :type unseen: int """ emit_async(catalog.MAIL_UNREAD_MESSAGES, str(unseen))
def start(self, restart=False): """ Launch the firewall using the privileged wrapper. :returns: True if the exitcode of calling the root helper in a subprocess is 0. :rtype: bool """ gateways = [gateway for gateway, port in self._remotes] # XXX check for wrapper existence, check it's root owned etc. # XXX check that the iptables rules are in place. cmd = [self.BITMASK_ROOT, "firewall", "start"] cmd = check_root(cmd) if restart: cmd.append("restart") result = '<did not run>' if not os.path.isfile(self.BITMASK_ROOT): raise FirewallError('Could not find bitmask-root!') try: retcode, result = commands.getstatusoutput(' '.join(cmd + gateways)) except Exception: msg = 'Error launching the firewall' log.failure(msg) if NOT_ROOT: raise FirewallError(msg) finally: log.debug(result) emit_async(catalog.VPN_STATUS_CHANGED, "FW_ON") return True
def _set_status(self, address, status, error=None, unread=None): self._status[address] = { "status": status, "error": error, "unread": unread } emit_async(catalog.MAIL_STATUS_CHANGED, address)
def _set_status(self, address, status, error=None, keys=None): self._status[address] = { "status": status, "error": error, "keys": keys } emit_async(catalog.MAIL_STATUS_CHANGED, address)
def _route_msg(self, encrypt_and_sign_result, recipient, raw): """ Sends the msg using the ESMTPSenderFactory. :param encrypt_and_sign_result: A tuple containing the 'maybe' encrypted message and the recipient :type encrypt_and_sign_result: tuple """ message, recipient = encrypt_and_sign_result msg = message.as_string(False) d = None for sender in self._senders: if sender.can_send(recipient.dest.addrstr): self.log.debug('Sending message to %s with: %s' % (recipient, str(sender))) d = sender.send(recipient, msg) break if d is None: return self.sendError(Failure(), raw) emit_async(catalog.SMTP_SEND_MESSAGE_START, self._from_address, recipient.dest.addrstr) d.addCallback(self.sendSuccess) d.addErrback(self.sendError, raw) return d
def send_key(self): """ Send user's key to provider. Public key bound to user's is sent to provider, which will replace any prior keys for the same address in its database. :return: A Deferred which fires when the key is sent, or which fails with KeyNotFound if the key was not found in local database. :rtype: Deferred :raise UnsupportedKeyTypeError: if invalid key type """ if not self.token: self.log.debug('Token not available, scheduling ' 'a new key sending attempt...') yield task.deferLater(reactor, 5, self.send_key) self.log.info('Sending public key to server') key = yield self.get_key(self._address, fetch_remote=False) yield self._nicknym.put_key(self.uid, key.key_data, self._api_uri, self._api_version) emit_async(catalog.KEYMANAGER_DONE_UPLOADING_KEYS, self._address) self.log.info('Key sent to server') defer.returnValue(key)
def gen_key(self, ktype): """ Generate a key of type ktype bound to the user's address. :param ktype: The type of the key. :type ktype: subclass of EncryptionKey :return: A Deferred which fires with the generated EncryptionKey. :rtype: Deferred :raise UnsupportedKeyTypeError: if invalid key type """ self._assert_supported_key_type(ktype) _keys = self._wrapper_map[ktype] def signal_finished(key): emit_async( catalog.KEYMANAGER_FINISHED_KEY_GENERATION, self._address) return key emit_async(catalog.KEYMANAGER_STARTED_KEY_GENERATION, self._address) d = _keys.gen_key(self._address) d.addCallback(signal_finished) return d
def __cb_signal_unread_to_ui(self, unseen): """ Send the unread signal to UI. :param unseen: number of unseen messages. :type unseen: int """ emit_async(catalog.MAIL_UNREAD_MESSAGES, self.store.uuid, str(unseen))
def _route_msg(self, encrypt_and_sign_result): """ Sends the msg using the ESMTPSenderFactory. :param encrypt_and_sign_result: A tuple containing the 'maybe' encrypted message and the recipient :type encrypt_and_sign_result: tuple """ message, recipient = encrypt_and_sign_result log.msg("Connecting to SMTP server %s:%s" % (self._host, self._port)) msg = message.as_string(False) # we construct a defer to pass to the ESMTPSenderFactory d = defer.Deferred() d.addCallbacks(self.sendSuccess, self.sendError) # we don't pass an ssl context factory to the ESMTPSenderFactory # because ssl will be handled by reactor.connectSSL() below. factory = smtp.ESMTPSenderFactory( "", # username is blank, no client auth here "", # password is blank, no client auth here self._from_address, recipient.dest.addrstr, StringIO(msg), d, heloFallback=True, requireAuthentication=False, requireTransportSecurity=True) factory.domain = __version__ emit_async(catalog.SMTP_SEND_MESSAGE_START, recipient.dest.addrstr) reactor.connectSSL( self._host, self._port, factory, contextFactory=SSLContextFactory(self._cert, self._key))
def get_key(self, address, ktype, private=False, fetch_remote=True): """ Return a key of type ktype bound to address. First, search for the key in local storage. If it is not available, then try to fetch from nickserver. :param address: The address bound to the key. :type address: str :param ktype: The type of the key. :type ktype: subclass of EncryptionKey :param private: Look for a private key instead of a public one? :type private: bool :param fetch_remote: If key not found in local storage try to fetch from nickserver :type fetch_remote: bool :return: A Deferred which fires with an EncryptionKey of type ktype bound to address, or which fails with KeyNotFound if no key was found neither locally or in keyserver. :rtype: Deferred :raise UnsupportedKeyTypeError: if invalid key type """ self._assert_supported_key_type(ktype) logger.debug("getting key for %s" % (address,)) leap_assert( ktype in self._wrapper_map, 'Unkown key type: %s.' % str(ktype)) _keys = self._wrapper_map[ktype] emit_async(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) def key_found(key): emit_async(catalog.KEYMANAGER_KEY_FOUND, address) return key def key_not_found(failure): if not failure.check(KeyNotFound): return failure emit_async(catalog.KEYMANAGER_KEY_NOT_FOUND, address) # we will only try to fetch a key from nickserver if fetch_remote # is True and the key is not private. if fetch_remote is False or private is True: return failure emit_async(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) d = self._fetch_keys_from_server(address) d.addCallback( lambda _: _keys.get_key(address, private=False)) d.addCallback(key_found) return d # return key if it exists in local database d = _keys.get_key(address, private=private) d.addCallbacks(key_found, key_not_found) return d
def if_key_not_found_send_unencrypted(failure, message): failure.trap(KeyNotFound, KeyAddressMismatch) log.msg('Will send unencrypted message to %s.' % to_address) emit_async(catalog.SMTP_START_SIGN, self._from_address) d = self._sign(message, from_address) d.addCallback(signal_sign) return d
def sendSuccess(self, dest_addrstr): """ Callback for a successful send. """ fromaddr = self._from_address self.log.info('Message sent from %s to %s' % (fromaddr, dest_addrstr)) emit_async(catalog.SMTP_SEND_MESSAGE_SUCCESS, fromaddr, dest_addrstr)
def if_key_not_found_send_unencrypted(failure, message): failure.trap(KeyNotFound, KeyAddressMismatch) self.log.info('Will send unencrypted message to %s.' % to_address) emit_async(catalog.SMTP_START_SIGN, self._from_address, to_address) d = self._sign(message, from_address) d.addCallback(signal_sign) return d
def __cb_signal_unread_to_ui(self, unseen): """ Send the unread signal to UI. :param unseen: number of unseen messages. :type unseen: int """ # TODO it might make sense to modify the event so that # it receives both the mailbox name AND the number of unread messages. emit_async(catalog.MAIL_UNREAD_MESSAGES, self.store.uuid, str(unseen))
def process_decrypted(res): if isinstance(res, tuple): decrdata, _ = res success = True else: decrdata = "" success = False emit_async(catalog.MAIL_MSG_DECRYPTED, "1" if success else "0") return self._process_decrypted_doc(doc, decrdata)
def connectionLost(self): """ Log an error when the connection is lost. """ log.msg("Connection lost unexpectedly!") log.err() emit_async(catalog.SMTP_CONNECTION_LOST, self._user.dest.addrstr) # unexpected loss of connection; don't save self._lines = []
def connectionLost(self): """ Log an error when the connection is lost. """ self.log.error('Connection lost unexpectedly!') emit_async(catalog.SMTP_CONNECTION_LOST, self._userid, self._user.dest.addrstr) # unexpected loss of connection; don't save self._lines = []
def process_decrypted(res): if isinstance(res, tuple): decrdata, _ = res success = True else: decrdata = "" success = False emit_async(catalog.MAIL_MSG_DECRYPTED, self._userid, "1" if success else "0") return self._process_decrypted_doc(doc, decrdata)
def msgSavedCallback(result): def signal_deleted(doc_id): emit_async(catalog.MAIL_MSG_DELETED_INCOMING, self._userid) return doc_id emit_async(catalog.MAIL_MSG_SAVED_LOCALLY, self._userid) d = self._delete_incoming_message(doc) d.addCallback(signal_deleted) return d
def run_service(store, **kwargs): """ Main entry point to run the service from the client. :param store: a soledad instance :returns: the port as returned by the reactor when starts listening, and the factory for the protocol. """ leap_check(store, "store cannot be None") # XXX this can also be a ProxiedObject, FIXME # leap_assert_type(store, Soledad) port = kwargs.get('port', IMAP_PORT) userid = kwargs.get('userid', None) leap_check(userid is not None, "need an user id") uuid = store.uuid factory = LeapIMAPFactory(uuid, userid, store) try: interface = "localhost" # don't bind just to localhost if we are running on docker since we # won't be able to access imap from the host if os.environ.get("LEAP_DOCKERIZED"): interface = '' tport = reactor.listenTCP(port, factory, interface=interface) except CannotListenError: logger.error("IMAP Service failed to start: " "cannot listen in port %s" % (port,)) except Exception as exc: logger.error("Error launching IMAP service: %r" % (exc,)) else: # all good. if DO_MANHOLE: # TODO get pass from env var.too. manhole_factory = manhole.getManholeFactory( {'f': factory, 'a': factory.theAccount, 'gm': factory.theAccount.getMailbox}, "boss", "leap") reactor.listenTCP(manhole.MANHOLE_PORT, manhole_factory, interface="127.0.0.1") logger.debug("IMAP4 Server is RUNNING in port %s" % (port,)) emit_async(catalog.IMAP_SERVICE_STARTED, str(port)) # FIXME -- change service signature return tport, factory # not ok, signal error. emit_async(catalog.IMAP_SERVICE_FAILED_TO_START, str(port))
def sendSuccess(self, smtp_sender_result): """ Callback for a successful send. :param smtp_sender_result: The result from the ESMTPSender from _route_msg :type smtp_sender_result: tuple(int, list(tuple)) """ dest_addrstr = smtp_sender_result[1][0][0] log.msg('Message sent to %s' % dest_addrstr) emit_async(catalog.SMTP_SEND_MESSAGE_SUCCESS, dest_addrstr)
def sendSuccess(self, smtp_sender_result): """ Callback for a successful send. :param smtp_sender_result: The result from the ESMTPSender from _route_msg :type smtp_sender_result: tuple(int, list(tuple)) """ dest_addrstr = smtp_sender_result[1][0][0] fromaddr = self._from_address log.msg('Message sent from %s to %s' % (fromaddr, dest_addrstr)) emit_async(catalog.SMTP_SEND_MESSAGE_SUCCESS, fromaddr, dest_addrstr)
def get_key(self, address, private=False, fetch_remote=True): """ Return a key bound to address. First, search for the key in local storage. If it is not available, then try to fetch from nickserver. :param address: The address bound to the key. :type address: str :param private: Look for a private key instead of a public one? :type private: bool :param fetch_remote: If key not found in local storage try to fetch from nickserver :type fetch_remote: bool :return: A Deferred which fires with an EncryptionKey bound to address, or which fails with KeyNotFound if no key was found neither locally or in keyserver or fail with KeyVersionError if the key has a format not supported by this version of KeyManager :rtype: Deferred :raise UnsupportedKeyTypeError: if invalid key type """ logger.debug("getting key for %s" % (address, )) emit_async(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) def key_found(key): emit_async(catalog.KEYMANAGER_KEY_FOUND, address) return key def key_not_found(failure): if not failure.check(KeyNotFound): return failure emit_async(catalog.KEYMANAGER_KEY_NOT_FOUND, address) # we will only try to fetch a key from nickserver if fetch_remote # is True and the key is not private. if fetch_remote is False or private is True: return failure emit_async(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) d = self._fetch_keys_from_server(address) d.addCallback( lambda _: self._openpgp.get_key(address, private=False)) d.addCallback(key_found) return d # return key if it exists in local database d = self._openpgp.get_key(address, private=private) d.addCallbacks(key_found, key_not_found) return d
def not_found(failure): failure.trap(KeyNotFound) # if key was not found, check config to see if will send anyway if self._encrypted_only: emit_async(catalog.SMTP_RECIPIENT_REJECTED, user.dest.addrstr) raise smtp.SMTPBadRcpt(user.dest.addrstr) log.msg("Warning: will send an unencrypted message (because " "encrypted_only' is set to False).") emit_async( catalog.SMTP_RECIPIENT_ACCEPTED_UNENCRYPTED, user.dest.addrstr)
def set_status(self, status, errcode): if status in ("AUTH", "WAIT", "CONNECTING", "GET_CONFIG", "ASSIGN_IP", "ADD_ROUTES", "RECONNECTING"): status = "starting" elif status == "EXITING": status = "stopping" elif status == "CONNECTED": status = "on" self._status = status self.errcode = errcode emit_async(catalog.VPN_STATUS_CHANGED)
def stop(self): """ Tear the firewall down using the privileged wrapper. """ cmd = [self.BITMASK_ROOT, "firewall", "stop"] cmd = check_root(cmd) exitCode = subprocess.call(cmd) emit_async(catalog.VPN_STATUS_CHANGED, "FW_OFF") if exitCode == 0: return True else: return False
def not_found(failure): failure.trap(KeyNotFound) # if key was not found, check config to see if will send anyway if self._encrypted_only: emit_async(catalog.SMTP_RECIPIENT_REJECTED, self._userid, user.dest.addrstr) raise smtp.SMTPBadRcpt(user.dest.addrstr) self.log.warn('Warning: will send an unencrypted message (because ' '"encrypted_only" is set to False).') emit_async(catalog.SMTP_RECIPIENT_ACCEPTED_UNENCRYPTED, self._userid, user.dest.addrstr)
def _fetch_remotely(passthru): # we will only try to fetch a key from nickserver if fetch_remote # is True and the key is not private. if fetch_remote is False or private is True: return passthru self.log.debug('Fetching remotely key for %s.' % address) emit_async(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) d = self._fetch_keys_from_server_and_store_local(address) d.addCallback( lambda _: self._openpgp.get_key(address, private=False)) d.addCallback(key_found) return d
def setup_smtp_gateway(port, userid, keymanager, smtp_host, smtp_port, smtp_cert, smtp_key, encrypted_only): """ Setup SMTP gateway to run with Twisted. This function sets up the SMTP gateway configuration and the Twisted reactor. :param port: The port in which to run the server. :type port: int :param userid: The user currently logged in :type userid: str :param keymanager: A Key Manager from where to get recipients' public keys. :type keymanager: leap.common.keymanager.KeyManager :param smtp_host: The hostname of the remote SMTP server. :type smtp_host: str :param smtp_port: The port of the remote SMTP server. :type smtp_port: int :param smtp_cert: The client certificate for authentication. :type smtp_cert: str :param smtp_key: The client key for authentication. :type smtp_key: str :param encrypted_only: Whether the SMTP gateway should send unencrypted mail or not. :type encrypted_only: bool :returns: tuple of SMTPFactory, twisted.internet.tcp.Port """ # configure the use of this service with twistd outgoing_mail = OutgoingMail( userid, keymanager, smtp_cert, smtp_key, smtp_host, smtp_port) factory = SMTPFactory(userid, keymanager, encrypted_only, outgoing_mail) try: interface = "localhost" # don't bind just to localhost if we are running on docker since we # won't be able to access smtp from the host if os.environ.get("LEAP_DOCKERIZED"): interface = '' tport = reactor.listenTCP(port, factory, interface=interface) emit_async(catalog.SMTP_SERVICE_STARTED, str(port)) return factory, tport except CannotListenError: logger.error("STMP Service failed to start: " "cannot listen in port %s" % port) emit_async(catalog.SMTP_SERVICE_FAILED_TO_START, str(port)) except Exception as exc: logger.error("Unhandled error while launching smtp gateway service") logger.exception(exc)
def run_service(soledad_sessions, port=IMAP_PORT): """ Main entry point to run the service from the client. :param soledad_sessions: a dict-like object, containing instances of a Store (soledad instances), indexed by userid. :returns: the port as returned by the reactor when starts listening, and the factory for the protocol. :rtype: tuple """ factory = LeapIMAPFactory(soledad_sessions) try: interface = "localhost" # don't bind just to localhost if we are running on docker since we # won't be able to access imap from the host if os.environ.get("LEAP_DOCKERIZED"): interface = '' # TODO use Endpoints !!! tport = reactor.listenTCP(port, factory, interface=interface) except CannotListenError: logger.error("IMAP Service failed to start: " "cannot listen in port %s" % (port, )) except Exception as exc: logger.error("Error launching IMAP service: %r" % (exc, )) else: # all good. if DO_MANHOLE: # TODO get pass from env var.too. manhole_factory = manhole.getManholeFactory( { 'f': factory, 'gm': factory.theAccount.getMailbox }, "boss", "leap") # TODO use Endpoints !!! reactor.listenTCP(manhole.MANHOLE_PORT, manhole_factory, interface="127.0.0.1") logger.debug("IMAP4 Server is RUNNING in port %s" % (port, )) emit_async(catalog.IMAP_SERVICE_STARTED, str(port)) # FIXME -- change service signature return tport, factory # not ok, signal error. emit_async(catalog.IMAP_SERVICE_FAILED_TO_START, str(port))
def msgSavedCallback(result): if empty(result): return for listener in self._listeners: listener(result) def signal_deleted(doc_id): emit_async(catalog.MAIL_MSG_DELETED_INCOMING) return doc_id emit_async(catalog.MAIL_MSG_SAVED_LOCALLY) d = self._delete_incoming_message(doc) d.addCallback(signal_deleted) return d
def stop(self): """ Tear the firewall down using the privileged wrapper. """ if IS_MAC: # We don't support Mac so far return True exitCode = subprocess.call( ["pkexec", self.BITMASK_ROOT, "firewall", "stop"]) emit_async(catalog.VPN_STATUS_CHANGED) if exitCode == 0: return True else: return False
def run_service(soledad_sessions, port=IMAP_PORT): """ Main entry point to run the service from the client. :param soledad_sessions: a dict-like object, containing instances of a Store (soledad instances), indexed by userid. :returns: the port as returned by the reactor when starts listening, and the factory for the protocol. :rtype: tuple """ factory = LeapIMAPFactory(soledad_sessions) try: interface = "localhost" # don't bind just to localhost if we are running on docker since we # won't be able to access imap from the host if os.environ.get("LEAP_DOCKERIZED"): interface = '' # TODO use Endpoints !!! tport = reactor.listenTCP(port, factory, interface=interface) except CannotListenError: logger.error("IMAP Service failed to start: " "cannot listen in port %s" % (port,)) except Exception as exc: logger.error("Error launching IMAP service: %r" % (exc,)) else: # all good. if DO_MANHOLE: # TODO get pass from env var.too. manhole_factory = manhole.getManholeFactory( {'f': factory, 'gm': factory.theAccount.getMailbox}, "boss", "leap") # TODO use Endpoints !!! reactor.listenTCP(manhole.MANHOLE_PORT, manhole_factory, interface="127.0.0.1") logger.debug("IMAP4 Server is RUNNING in port %s" % (port,)) emit_async(catalog.IMAP_SERVICE_STARTED, str(port)) # FIXME -- change service signature return tport, factory # not ok, signal error. emit_async(catalog.IMAP_SERVICE_FAILED_TO_START, str(port))
def _process_incoming_mail(self, doclist): """ Iterates through the doclist, checks if each doc looks like a message, and yields a deferred that will decrypt and process the message. :param doclist: iterable with msg documents. :type doclist: iterable. :returns: a list of deferreds for individual messages. """ self.log.info('Processing incoming mail') if not doclist: self.log.debug("no incoming messages found") return num_mails = len(doclist) deferreds = [] for index, doc in enumerate(doclist): self.log.debug( 'Processing Incoming Message: %d of %d' % (index + 1, num_mails)) emit_async(catalog.MAIL_MSG_PROCESSING, self._userid, str(index), str(num_mails)) keys = doc.content.keys() # TODO Compatibility check with the index in pre-0.6 mx # that does not write the ERROR_DECRYPTING_KEY # This should be removed in 0.7 # TODO deprecate this already has_errors = doc.content.get(fields.ERROR_DECRYPTING_KEY, None) if has_errors is None: warnings.warn('JUST_MAIL_COMPAT_IDX will be deprecated!', DeprecationWarning) if has_errors: self.log.debug('Skipping message with decrypting errors...') elif self._is_msg(keys): # TODO this pipeline is a bit obscure! d = self._decrypt_doc(doc) d.addCallbacks(self._add_message_locally, self._errback) deferreds.append(d) d = defer.gatherResults(deferreds, consumeErrors=True) d.addCallback(lambda _: doclist) return d
def _signal_fetch_to_ui(self, doclist): """ Send leap events to ui. :param doclist: iterable with msg documents. :type doclist: iterable. :returns: doclist :rtype: iterable """ if doclist: fetched_ts = time.mktime(time.gmtime()) num_mails = len(doclist) if doclist is not None else 0 if num_mails != 0: log.msg("there are %s mails" % (num_mails,)) emit_async(catalog.MAIL_FETCHED_INCOMING, self._userid, str(num_mails), str(fetched_ts)) return doclist
def key_not_found(failure): if not failure.check(KeyNotFound): return failure emit_async(catalog.KEYMANAGER_KEY_NOT_FOUND, address) # we will only try to fetch a key from nickserver if fetch_remote # is True and the key is not private. if fetch_remote is False or private is True: return failure emit_async(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) d = self._fetch_keys_from_server(address) d.addCallback( lambda _: _keys.get_key(address, private=False)) d.addCallback(key_found) return d
def send(pubkey): data = {self.PUBKEY_KEY: pubkey.key_data} uri = "%s/%s/users/%s.json" % (self._api_uri, self._api_version, self._uid) d = self._put(uri, data) d.addCallback(lambda _: emit_async( catalog.KEYMANAGER_DONE_UPLOADING_KEYS, self._address)) return d
def key_not_found(failure): if not failure.check(keymanager_errors.KeyNotFound): return failure emit_async(catalog.KEYMANAGER_KEY_NOT_FOUND, address) # we will only try to fetch a key from nickserver if fetch_remote # is True and the key is not private. if fetch_remote is False or private is True: return failure emit_async(catalog.KEYMANAGER_LOOKING_FOR_KEY, address) d = self._fetch_keys_from_server_and_store_local(address) d.addCallback( lambda _: self._openpgp.get_key(address, private=False)) d.addCallback(key_found) return d
def _signal_fetch_to_ui(self, doclist): """ Send leap events to ui. :param doclist: iterable with msg documents. :type doclist: iterable. :returns: doclist :rtype: iterable """ if doclist: fetched_ts = time.mktime(time.gmtime()) num_mails = len(doclist) if doclist is not None else 0 if num_mails != 0: self.log.info('There are {0!s} mails'.format(num_mails)) emit_async(catalog.MAIL_FETCHED_INCOMING, self._userid, str(num_mails), str(fetched_ts)) return doclist
def _process_doclist(self, doclist): """ Iterates through the doclist, checks if each doc looks like a message, and yields a deferred that will decrypt and process the message. :param doclist: iterable with msg documents. :type doclist: iterable. :returns: a list of deferreds for individual messages. """ log.msg('processing doclist') if not doclist: logger.debug("no docs found") return num_mails = len(doclist) deferreds = [] for index, doc in enumerate(doclist): logger.debug("processing doc %d of %d" % (index + 1, num_mails)) emit_async(catalog.MAIL_MSG_PROCESSING, self._userid, str(index), str(num_mails)) keys = doc.content.keys() # TODO Compatibility check with the index in pre-0.6 mx # that does not write the ERROR_DECRYPTING_KEY # This should be removed in 0.7 has_errors = doc.content.get(fields.ERROR_DECRYPTING_KEY, None) if has_errors is None: warnings.warn("JUST_MAIL_COMPAT_IDX will be deprecated!", DeprecationWarning) if has_errors: logger.debug("skipping msg with decrypting errors...") elif self._is_msg(keys): d = self._decrypt_doc(doc) d.addCallback(self._maybe_extract_keys) d.addCallbacks(self._add_message_locally, self._errback) deferreds.append(d) d = defer.gatherResults(deferreds, consumeErrors=True) d.addCallback(lambda _: doclist) return d
def run_service(soledad_sessions, keymanager_sessions, sendmail_opts, port=SMTP_PORT, factory=None): """ Main entry point to run the service from the client. :param soledad_sessions: a dict-like object, containing instances of a Store (soledad instances), indexed by userid. :param keymanager_sessions: a dict-like object, containing instances of Keymanager, indexed by userid. :param sendmail_opts: a dict-like object of sendmailOptions. :param factory: a factory for the protocol that will listen in the given port :returns: the port as returned by the reactor when starts listening, and the factory for the protocol. :rtype: tuple """ if not factory: factory = SMTPFactory(soledad_sessions, keymanager_sessions, sendmail_opts) try: interface = "localhost" # don't bind just to localhost if we are running on docker since we # won't be able to access smtp from the host if os.environ.get("LEAP_DOCKERIZED"): interface = '' # TODO Use Endpoints instead -------------------------------- tport = reactor.listenTCP(port, factory, interface=interface) emit_async(catalog.SMTP_SERVICE_STARTED, str(port)) return tport, factory except CannotListenError: logger.error("STMP Service failed to start: " "cannot listen in port %s" % port) emit_async(catalog.SMTP_SERVICE_FAILED_TO_START, str(port)) except Exception as exc: logger.error("Unhandled error while launching smtp gateway service") logger.error('%r' % exc)
def gen_key(self): """ Generate a key bound to the user's address. :return: A Deferred which fires with the generated EncryptionKey. :rtype: Deferred :raise UnsupportedKeyTypeError: if invalid key type """ def signal_finished(key): emit_async(catalog.KEYMANAGER_FINISHED_KEY_GENERATION, self._address) return key emit_async(catalog.KEYMANAGER_STARTED_KEY_GENERATION, self._address) d = self._openpgp.gen_key(self._address) d.addCallback(signal_finished) return d
def send(pubkey): data = { self.PUBKEY_KEY: pubkey.key_data } uri = "%s/%s/users/%s.json" % ( self._api_uri, self._api_version, self._uid) d = self._put(uri, data) d.addCallback(lambda _: emit_async(catalog.KEYMANAGER_DONE_UPLOADING_KEYS, self._address)) return d
def run_service(soledad_sessions, port=IMAP_PORT, factory=None): """ Main entry point to run the service from the client. :param soledad_sessions: a dict-like object, containing instances of a Store (soledad instances), indexed by userid. :returns: the port as returned by the reactor when starts listening, and the factory for the protocol. :rtype: tuple """ if not factory: factory = LeapIMAPFactory(soledad_sessions) try: interface = "localhost" # don't bind just to localhost if we are running on docker since we # won't be able to access imap from the host if os.environ.get("LEAP_DOCKERIZED"): interface = '' # TODO use Endpoints !!! tport = reactor.listenTCP(port, factory, interface=interface) except CannotListenError: log.error('IMAP Service failed to start: ' 'cannot listen in port %s' % (port, )) except Exception as exc: log.error("Error launching IMAP service: %r" % (exc, )) else: # all good. log.debug('IMAP4 Server is RUNNING in port %s' % (port, )) emit_async(catalog.IMAP_SERVICE_STARTED, str(port)) # FIXME -- change service signature return tport, factory # not ok, signal error. emit_async(catalog.IMAP_SERVICE_FAILED_TO_START, str(port))
def run_service(soledad_sessions, keymanager_sessions, sendmail_opts, port=SMTP_PORT): """ Main entry point to run the service from the client. :param soledad_sessions: a dict-like object, containing instances of a Store (soledad instances), indexed by userid. :param keymanager_sessions: a dict-like object, containing instances of Keymanager, indexed by userid. :param sendmail_opts: a dict-like object of sendmailOptions. :returns: the port as returned by the reactor when starts listening, and the factory for the protocol. :rtype: tuple """ factory = SMTPFactory(soledad_sessions, keymanager_sessions, sendmail_opts) try: interface = "localhost" # don't bind just to localhost if we are running on docker since we # won't be able to access smtp from the host if os.environ.get("LEAP_DOCKERIZED"): interface = '' # TODO Use Endpoints instead -------------------------------- tport = reactor.listenTCP(port, factory, interface=interface) emit_async(catalog.SMTP_SERVICE_STARTED, str(port)) return factory, tport except CannotListenError: logger.error("STMP Service failed to start: " "cannot listen in port %s" % port) emit_async(catalog.SMTP_SERVICE_FAILED_TO_START, str(port)) except Exception as exc: logger.error("Unhandled error while launching smtp gateway service") logger.exception(exc)
def signal_encrypt_sign(newmsg): emit_async(catalog.SMTP_END_ENCRYPT_AND_SIGN, "%s,%s" % (self._from_address, to_address)) return newmsg, recipient
def key_found(key): emit_async(catalog.KEYMANAGER_KEY_FOUND, address) return key
def found(_): log.msg("Accepting mail for %s..." % user.dest.addrstr) emit_async(catalog.SMTP_RECIPIENT_ACCEPTED_ENCRYPTED, self._userid, user.dest.addrstr)
def _maybe_encrypt_and_sign(self, raw, recipient): """ Attempt to encrypt and sign the outgoing message. The behaviour of this method depends on: 1. the original message's content-type, and 2. the availability of the recipient's public key. If the original message's content-type is "multipart/encrypted", then the original message is not altered. For any other content-type, the method attempts to fetch the recipient's public key. If the recipient's public key is available, the message is encrypted and signed; otherwise it is only signed. Note that, if the C{encrypted_only} configuration is set to True and the recipient's public key is not available, then the recipient address would have been rejected in SMTPDelivery.validateTo(). The following table summarizes the overall behaviour of the gateway: +---------------------------------------------------+----------------+ | content-type | rcpt pubkey | enforce encr. | action | +---------------------+-------------+---------------+----------------+ | multipart/encrypted | any | any | pass | | other | available | any | encrypt + sign | | other | unavailable | yes | reject | | other | unavailable | no | sign | +---------------------+-------------+---------------+----------------+ :param raw: The raw message :type raw: str :param recipient: The recipient for the message :type: recipient: smtp.User :return: A Deferred that will be fired with a MIMEMultipart message and the original recipient Message :rtype: Deferred """ # pass if the original message's content-type is "multipart/encrypted" origmsg = Parser().parsestr(raw) if origmsg.get_content_type() == 'multipart/encrypted': return defer.success((origmsg, recipient)) from_address = validate_address(self._from_address) username, domain = from_address.split('@') to_address = validate_address(recipient.dest.addrstr) def maybe_encrypt_and_sign(message): d = self._encrypt_and_sign(message, to_address, from_address) d.addCallbacks(signal_encrypt_sign, if_key_not_found_send_unencrypted, errbackArgs=(message,)) return d def signal_encrypt_sign(newmsg): emit_async(catalog.SMTP_END_ENCRYPT_AND_SIGN, "%s,%s" % (self._from_address, to_address)) return newmsg, recipient def if_key_not_found_send_unencrypted(failure, message): failure.trap(KeyNotFound, KeyAddressMismatch) log.msg('Will send unencrypted message to %s.' % to_address) emit_async(catalog.SMTP_START_SIGN, self._from_address) d = self._sign(message, from_address) d.addCallback(signal_sign) return d def signal_sign(newmsg): emit_async(catalog.SMTP_END_SIGN, self._from_address) return newmsg, recipient log.msg("Will encrypt the message with %s and sign with %s." % (to_address, from_address)) emit_async(catalog.SMTP_START_ENCRYPT_AND_SIGN, "%s,%s" % (self._from_address, to_address)) d = self._maybe_attach_key(origmsg, from_address, to_address) d.addCallback(maybe_encrypt_and_sign) return d
def _signal_invalid_auth(failure): failure.trap(InvalidAuthTokenError) # if the token is invalid, send an event so the GUI can # disable mail and show an error message. emit_async(catalog.SOLEDAD_INVALID_AUTH_TOKEN, self._userid)
def signal_sign(newmsg): emit_async(catalog.SMTP_END_SIGN, self._from_address) return newmsg, recipient
def _signal_unread_to_ui(self, *args): """ Sends unread event to ui. """ emit_async(catalog.MAIL_UNREAD_MESSAGES, self._userid, str(self._inbox_collection.count_unseen()))
def signal_deleted(doc_id): emit_async(catalog.MAIL_MSG_DELETED_INCOMING, self._userid) return doc_id
def signal_finished(key): emit_async( catalog.KEYMANAGER_FINISHED_KEY_GENERATION, self._address) return key