summaryrefslogtreecommitdiff
path: root/service/pixelated
diff options
context:
space:
mode:
Diffstat (limited to 'service/pixelated')
-rw-r--r--service/pixelated/adapter/model/mail.py2
-rw-r--r--service/pixelated/assets/welcome.mail19
-rw-r--r--service/pixelated/config/__init__.py3
-rw-r--r--service/pixelated/config/app_factory.py3
-rw-r--r--service/pixelated/config/args.py22
-rw-r--r--service/pixelated/config/leap_cert.py6
-rw-r--r--service/pixelated/config/register.py10
-rw-r--r--service/pixelated/maintenance.py169
-rw-r--r--service/pixelated/resources/root_resource.py2
-rw-r--r--service/pixelated/resources/sync_info_resource.py46
-rw-r--r--service/pixelated/support/error_handler.py27
-rw-r--r--service/pixelated/support/ext_sync.py23
12 files changed, 262 insertions, 70 deletions
diff --git a/service/pixelated/adapter/model/mail.py b/service/pixelated/adapter/model/mail.py
index 8e7b745c..b0b56fec 100644
--- a/service/pixelated/adapter/model/mail.py
+++ b/service/pixelated/adapter/model/mail.py
@@ -341,7 +341,7 @@ class PixelatedMail(Mail):
date = received.split(";")[-1].strip()
else:
# we can't get a date for this mail, so lets just use now
- logger.warning('Encountered a mail with missing date and received header fields. Subject %s' % self.hdoc.content.get('subject', None))
+ logger.warning('Encountered a mail with missing date and received header fields. ID %s' % self.fdoc.content.get('uid', None))
date = pixelated.support.date.iso_now()
return dateparser.parse(date).isoformat()
except (ValueError, TypeError) as e:
diff --git a/service/pixelated/assets/welcome.mail b/service/pixelated/assets/welcome.mail
index 346bfaf6..e85694f1 100644
--- a/service/pixelated/assets/welcome.mail
+++ b/service/pixelated/assets/welcome.mail
@@ -19,10 +19,12 @@ To compose a message look for the big blue button on the top left. You can add t
A bit more about Pixelated
Pixelated is an open source project licensed under AGPL 3.0. It is composed of 3 main parts, the User Agent (what you are using right now), the Dispatcher (what allows you to log in with different accounts to the same instance) and the Platform (which provides the email service you will use to send and receive messages - the server behind the @ sign on your new mail address). You can learn more by visiting https://pixelated-project.org/.
-About this message and understanding examples of message status
-This message was not encrypted, in other words, it could have been read by others at some point during transmission.
-However you can check the authenticity of this message, because the message has a certified sender.
-Whether it is green, it means that the text you are reading has not changed on the way until delivered to you.
+About this message and encryption status
+This message was not encrypted, in other words, it could have been read by others at some point during transmission, like any other email client.
+To send encrypted messages you have to have the public keys of the recipients, in Pixelated you have 2 options:
+send an email to another Pixelated account: public keys are exchanged by default.
+send an email to another email provider: you should had exchanged public keys with the recipients previously.
+
Enjoy your secure messaging!
@@ -60,10 +62,11 @@ s). You can learn more by visiting <a src=3D"https://pixelated-project.org/=
">https://pixelated-project.org/</a>.
</p>
<p>
-<b>About this message and understanding examples of message status</b><br>
-This message was not encrypted, in other words, it could have been read by others at some point during transmission.
-However you can check the authenticity of this message, because the message has a certified sender.
-Whether it is green, it means that the text you are reading has not changed on the way until delivered to you.
+<b>About this message and encryption status</b><br>
+This message was not encrypted, in other words, it could have been read by others at some point during transmission like any other email client.
+To send encrypted messages you have to have the public Keys of the recipients, in Pixelated you have 2 options:
+<p>- sending an email to another Pixelated account: public keys are exchanged by default.
+<br>- sending an email to another email provider: you should had exchanged public keys with the recipients previously.
</p>
<p>
Enjoy your secure messaging!
diff --git a/service/pixelated/config/__init__.py b/service/pixelated/config/__init__.py
index af264c77..f59d684b 100644
--- a/service/pixelated/config/__init__.py
+++ b/service/pixelated/config/__init__.py
@@ -36,8 +36,10 @@ import pixelated.support.ext_protobuf
import pixelated.support.ext_sqlcipher
import pixelated.support.ext_esmtp_sender_factory
import pixelated.support.ext_fetch
+import pixelated.support.ext_sync
import pixelated.support.ext_keymanager_fetch_key
import pixelated.support.ext_requests_urllib3
+from pixelated.support.error_handler import error_handler
def initialize():
@@ -74,6 +76,7 @@ def initialize():
d = deferToThread(init_soledad)
d.addCallback(stop_loading_app)
+ d.addErrback(error_handler)
reactor.callWhenRunning(load_app)
reactor.run()
diff --git a/service/pixelated/config/app_factory.py b/service/pixelated/config/app_factory.py
index 5dcf60cb..51f76741 100644
--- a/service/pixelated/config/app_factory.py
+++ b/service/pixelated/config/app_factory.py
@@ -53,9 +53,8 @@ def init_index_and_remove_dupes(querier, search_engine, mail_service):
return wrapper
-def update_info_sync_and_index_partial(sync_info_controller, search_engine, mail_service):
+def update_index_partial(search_engine, mail_service):
def wrapper(soledad_sync_status):
- sync_info_controller.set_sync_info(soledad_sync_status)
search_engine.index_mails(mails=mail_service.all_mails())
return wrapper
diff --git a/service/pixelated/config/args.py b/service/pixelated/config/args.py
index d3284fab..a5d19369 100644
--- a/service/pixelated/config/args.py
+++ b/service/pixelated/config/args.py
@@ -20,18 +20,24 @@ from pixelated.bitmask_libraries.config import DEFAULT_LEAP_HOME
def parse():
parser = argparse.ArgumentParser(description='Pixelated user agent.')
- parser.add_argument('--debug', action='store_true', help='DEBUG mode.')
- parser.add_argument('--dispatcher', help='run in organization mode, the credentials will be read from specified file', metavar='file')
- parser.add_argument('--dispatcher-stdin', help='run in organization mode, the credentials will be read from stdin', default=False, action='store_true', dest='dispatcher_stdin')
+
+ parser_add_default_arguments(parser)
+
parser.add_argument('--host', default='127.0.0.1', help='the host to run the user agent on')
parser.add_argument('--port', type=int, default=3333, help='the port to run the user agent on')
- parser.add_argument('--home', help='The folder where the user agent stores its data. Defaults to ~/.leap', default=DEFAULT_LEAP_HOME)
- parser.add_argument('-c', '--config', metavar='<configfile>', default=None, help='use specified file for credentials (for test purposes only)')
parser.add_argument('-sk', '--sslkey', metavar='<server.key>', default=None, help='use specified file as web server\'s SSL key (when using the user-agent together with the pixelated-dispatcher)')
parser.add_argument('-sc', '--sslcert', metavar='<server.crt>', default=None, help='use specified file as web server\'s SSL certificate (when using the user-agent together with the pixelated-dispatcher)')
- parser.add_argument('-lc', '--leap-cert', metavar='<leap.crt>', default=None, help='use specified file for LEAP cert authority certificate (url https://<provider-domain>/ca.crt)')
- parser.add_argument('--leap-cert-fingerprint', metavar='<leap certificate fingerprint>', default=None, help='use specified fingerprint to validate connection with leap provider', dest='leap_cert_fingerprint')
parser.add_argument('--register', metavar=('provider', 'username'),
- nargs=2, help='register a new username on the desired provider')
+ nargs=2, help='register a new username on the desired LEAP provider')
args = parser.parse_args()
return args
+
+
+def parser_add_default_arguments(parser):
+ parser.add_argument('--debug', action='store_true', help='DEBUG mode.')
+ parser.add_argument('--dispatcher', help='run in organization mode, the credentials will be read from specified file', metavar='file')
+ parser.add_argument('--dispatcher-stdin', help='run in organization mode, the credentials will be read from stdin', default=False, action='store_true', dest='dispatcher_stdin')
+ parser.add_argument('-c', '--config', metavar='<configfile>', default=None, help='use specified file for credentials (for test purposes only)')
+ parser.add_argument('--home', help='The folder where the user agent stores its data. Defaults to ~/.leap', default=DEFAULT_LEAP_HOME)
+ parser.add_argument('-lc', '--leap-provider-cert', metavar='<leap-provider.crt>', default=None, help='use specified file for LEAP provider cert authority certificate (url https://<LEAP-provider-domain>/ca.crt)')
+ parser.add_argument('-lf', '--leap-provider-cert-fingerprint', metavar='<leap provider certificate fingerprint>', default=None, help='use specified fingerprint to validate connection with LEAP provider', dest='leap_provider_cert_fingerprint')
diff --git a/service/pixelated/config/leap_cert.py b/service/pixelated/config/leap_cert.py
index 22f73720..568f76d9 100644
--- a/service/pixelated/config/leap_cert.py
+++ b/service/pixelated/config/leap_cert.py
@@ -18,9 +18,9 @@ import pixelated.bitmask_libraries.certs as certs
def init_leap_cert(args):
- if args.leap_cert_fingerprint is None:
- certs.LEAP_CERT = args.leap_cert or True
+ if args.leap_provider_cert_fingerprint is None:
+ certs.LEAP_CERT = args.leap_provider_cert or True
certs.LEAP_FINGERPRINT = None
else:
- certs.LEAP_FINGERPRINT = args.leap_cert_fingerprint
+ certs.LEAP_FINGERPRINT = args.leap_provider_cert_fingerprint
certs.LEAP_CERT = False
diff --git a/service/pixelated/config/register.py b/service/pixelated/config/register.py
index 3f93aa8d..d54b10ff 100644
--- a/service/pixelated/config/register.py
+++ b/service/pixelated/config/register.py
@@ -13,6 +13,7 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
+import re
from pixelated.bitmask_libraries.leap_srp import LeapAuthException
from pixelated.bitmask_libraries.register import register_new_user
@@ -20,6 +21,15 @@ from pixelated.bitmask_libraries.register import register_new_user
def register(username, server_name):
try:
+ validate_username(username)
register_new_user(username, server_name)
except LeapAuthException:
print('User already exists')
+ except ValueError:
+ print('Only lowercase letters, digits, . - and _ allowed.')
+
+
+def validate_username(username):
+ accepted_characters = '^[a-z0-9\-\_\.]*$'
+ if not re.match(accepted_characters, username):
+ raise ValueError
diff --git a/service/pixelated/maintenance.py b/service/pixelated/maintenance.py
new file mode 100644
index 00000000..8c20e097
--- /dev/null
+++ b/service/pixelated/maintenance.py
@@ -0,0 +1,169 @@
+#
+# Copyright (c) 2014 ThoughtWorks, Inc.
+#
+# Pixelated is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Pixelated is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+from functools import partial
+import sys
+import json
+import argparse
+import email
+
+from os.path import join
+from mailbox import mboxMessage
+from pixelated.config.app import App
+from pixelated.config import app_factory
+from pixelated.config.args import parser_add_default_arguments
+from pixelated.config.config_ua import config_user_agent
+from pixelated.config.dispatcher import config_dispatcher
+from pixelated.config.events_server import init_events_server
+from pixelated.config.loading_page import loading
+from pixelated.config.register import register
+from pixelated.config.logging_setup import init_logging
+from pixelated.config.leap_cert import init_leap_cert
+from pixelated.config.soledad import init_soledad_and_user_key
+from twisted.internet import reactor, defer
+from twisted.internet.threads import deferToThread
+
+from leap.mail.imap.memorystore import MemoryStore
+from leap.mail.imap.soledadstore import SoledadStore
+from leap.common.events import register, unregister, events_pb2 as proto
+
+# monkey patching some specifics
+import pixelated.support.ext_protobuf
+import pixelated.support.ext_sqlcipher
+import pixelated.support.ext_esmtp_sender_factory
+import pixelated.support.ext_fetch
+import pixelated.support.ext_keymanager_fetch_key
+import pixelated.support.ext_requests_urllib3
+
+
+def delete_all_mails(args):
+ leap_session, soledad = args
+ generation, docs = soledad.get_all_docs()
+
+ for doc in docs:
+ if doc.content.get('type', None) in ['head', 'cnt', 'flags']:
+ soledad.delete_doc(doc)
+
+ return args
+
+
+@defer.inlineCallbacks
+def load_mails(args, mail_paths):
+ leap_session, soledad = args
+ account = leap_session.account
+
+ for path in mail_paths:
+ print 'Loading mails from %s' % path
+ for root, dirs, files in os.walk(path):
+ mbx = account.getMailbox('INBOX')
+ for f in files:
+ with open(join(root, f), 'r') as fp:
+ m = email.message_from_file(fp)
+ flags = ("\\RECENT",)
+ r = yield mbx.addMessage(m.as_string(), flags=flags, notify_on_disk=False)
+ print 'Added message %s' % m.get('subject')
+ print m.as_string()
+
+ defer.returnValue(args)
+ return
+
+
+def dump_soledad(args):
+ leap_session, soledad = args
+
+ generation, docs = soledad.get_all_docs()
+
+ for doc in docs:
+ print doc
+ print '\n'
+
+ return args
+
+
+def initialize():
+ parser = argparse.ArgumentParser(description='pixelated maintenance')
+ parser_add_default_arguments(parser)
+ subparsers = parser.add_subparsers(help='commands', dest='command')
+ subparsers.add_parser('reset', help='reset account command')
+ mails_parser = subparsers.add_parser('load-mails', help='load mails into account')
+ mails_parser.add_argument('file', nargs='+', help='file(s) with mail data')
+
+ subparsers.add_parser('dump-soledad', help='dump the soledad database')
+ subparsers.add_parser('sync', help='sync the soledad database')
+
+ args = parser.parse_args()
+ app = App()
+
+ init_logging(args)
+ init_leap_cert(args)
+
+ if args.dispatcher or args.dispatcher_stdin:
+ config_dispatcher(app, args)
+ else:
+ config_user_agent(app, args)
+
+ init_events_server()
+
+ def execute_command():
+
+ def init_soledad():
+ return init_soledad_and_user_key(app, args.home)
+
+ def get_soledad_handle(leap_session):
+ soledad = leap_session.soledad_session.soledad
+
+ return leap_session, soledad
+
+ def soledad_sync(args):
+ leap_session, soledad = args
+
+ soledad.sync()
+
+ return args
+
+ d = deferToThread(init_soledad)
+ d.addCallback(get_soledad_handle)
+ d.addCallback(soledad_sync)
+ if args.command == 'reset':
+ d.addCallback(delete_all_mails)
+ elif args.command == 'load-mails':
+ d.addCallback(load_mails, args.file)
+ elif args.command == 'dump-soledad':
+ d.addCallback(dump_soledad)
+ elif args.command == 'sync':
+ # nothing to do here, sync is already part of the chain
+ pass
+ else:
+ print 'Unsupported command: %s' % args.command
+ d.addCallback(soledad_sync)
+ d.addCallback(shutdown)
+ d.addErrback(shutdown_on_error)
+
+ reactor.callWhenRunning(execute_command)
+ reactor.run()
+
+
+def shutdown(args):
+ reactor.stop()
+
+
+def shutdown_on_error(error):
+ print error
+ reactor.stop()
+
+if __name__ == 'main':
+ initialize()
diff --git a/service/pixelated/resources/root_resource.py b/service/pixelated/resources/root_resource.py
index b45bd00f..4c0c47ac 100644
--- a/service/pixelated/resources/root_resource.py
+++ b/service/pixelated/resources/root_resource.py
@@ -4,7 +4,6 @@ from pixelated.resources.contacts_resource import ContactsResource
from pixelated.resources.features_resource import FeaturesResource
from pixelated.resources.mail_resource import MailResource
from pixelated.resources.mails_resource import MailsResource
-from pixelated.resources.sync_info_resource import SyncInfoResource
from pixelated.resources.tags_resource import TagsResource
from pixelated.resources.keys_resource import KeysResource
from twisted.web.resource import Resource
@@ -28,7 +27,6 @@ class RootResource(Resource):
self.putChild('attachment', AttachmentsResource(querier))
self.putChild('contacts', ContactsResource(search_engine))
self.putChild('features', FeaturesResource())
- self.putChild('sync_info', SyncInfoResource())
self.putChild('tags', TagsResource(search_engine))
self.putChild('mails', MailsResource(mail_service, draft_service))
self.putChild('mail', MailResource(mail_service))
diff --git a/service/pixelated/resources/sync_info_resource.py b/service/pixelated/resources/sync_info_resource.py
deleted file mode 100644
index 791c5add..00000000
--- a/service/pixelated/resources/sync_info_resource.py
+++ /dev/null
@@ -1,46 +0,0 @@
-#
-# Copyright (c) 2014 ThoughtWorks, Inc.
-#
-# Pixelated is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Pixelated is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
-from pixelated.resources import respond_json
-from twisted.web.resource import Resource
-
-
-class SyncInfoResource(Resource):
-
- isLeaf = True
-
- def __init__(self):
- Resource.__init__(self)
- self.current = 0
- self.total = 0
-
- def _get_progress(self):
- if self.total == 0:
- return 0
- return self.current / float(self.total)
-
- def set_sync_info(self, soledad_sync_status):
- self.current, self.total = [int(x) for x in soledad_sync_status.content.split('/')]
-
- def render_GET(self, request):
- _sync_info = {
- 'is_syncing': self.current != self.total,
- 'count': {
- 'current': self.current,
- 'total': self.total,
- 'progress': self._get_progress()
- }
- }
- return respond_json(_sync_info, request)
diff --git a/service/pixelated/support/error_handler.py b/service/pixelated/support/error_handler.py
new file mode 100644
index 00000000..1a0e1a11
--- /dev/null
+++ b/service/pixelated/support/error_handler.py
@@ -0,0 +1,27 @@
+#
+# Copyright (c) 2014 ThoughtWorks, Inc.
+#
+# Pixelated is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Pixelated is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with Pixelated. If not, see <http://www.gnu.org/licenses/>.
+from requests.exceptions import SSLError
+
+
+def error_handler(excp):
+ if excp.type is SSLError:
+ print """
+ SSL Error: Please check your certificates or read our wiki for further info:
+ https://github.com/pixelated-project/pixelated-user-agent/wiki/Configuring-and-using-SSL-Certificates-for-LEAP-provider
+ Error reference: %s
+ """ % excp.getErrorMessage()
+ else:
+ raise excp
diff --git a/service/pixelated/support/ext_sync.py b/service/pixelated/support/ext_sync.py
new file mode 100644
index 00000000..d35eed3e
--- /dev/null
+++ b/service/pixelated/support/ext_sync.py
@@ -0,0 +1,23 @@
+import leap.soledad.client as client
+import logging
+import urlparse
+from leap.soledad.client.events import (
+ SOLEDAD_DONE_DATA_SYNC,
+ signal
+)
+
+
+def patched_sync(self, defer_decryption=True):
+ if self._db:
+ try:
+ local_gen = self._db.sync(
+ urlparse.urljoin(self.server_url, 'user-%s' % self._uuid),
+ creds=self._creds, autocreate=False,
+ defer_decryption=defer_decryption)
+ signal(SOLEDAD_DONE_DATA_SYNC, self._uuid)
+ return local_gen
+ except Exception as e:
+ client.logger.error("Soledad exception when syncing: %s - %s" % (e.__class__.__name__, e.message))
+
+
+client.Soledad.sync = patched_sync