1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
# SPDX-License-Identifier: GPL-2.0+
import asyncio
import itertools
import logging
import os
from typing import TYPE_CHECKING, Any, Dict, List
import pyinotify
if TYPE_CHECKING:
from . import messages, mailbox, oauth
logger: logging.Logger
class Config(object):
"""Program configuration and general global state"""
message_db_dir = "~/mail/.cms/"
trace_file: Any = None
web_app: "oauth.WebServer"
logger: logging.Logger
loop: asyncio.AbstractEventLoop
watch_manager: pyinotify.WatchManager
msgdb: "messages.MessageDB"
cloud_mboxes: "List[mailbox.Mailbox]"
local_mboxes: "List[mailbox.Mailbox]"
def _create_logger(self):
global logger
logger = logging.getLogger('cloud-mdir-sync')
logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setFormatter(
logging.Formatter(fmt='%(asctime)s %(levelname)-8s %(message)s',
datefmt='%m-%d %H:%M:%S'))
ch.setLevel(logging.DEBUG)
logger.addHandler(ch)
self.logger = logger
def __init__(self):
self._create_logger()
self.cloud_mboxes = []
self.local_mboxes = []
self.async_tasks = []
self.message_db_dir = os.path.expanduser(self.message_db_dir)
self.direct_message = self._direct_message
def load_config(self, fn):
"""The configuration file is a python script that we execute with
capitalized functions of this class injected into it"""
fn = os.path.expanduser(fn)
with open(fn, "r") as F:
pyc = compile(source=F.read(), filename=fn, mode="exec")
g = {"cfg": self}
for k in dir(self):
if k[0].isupper():
g[k] = getattr(self, k)
eval(pyc, g)
@property
def storage_key(self):
"""The storage key is used with fernet to manage the authentication
data, which is stored to disk using symmetric encryption. The
decryption key is keld by the system keyring in some secure storage.
On Linux desktop systems this is likely to be something like
gnome-keyring."""
import keyring
from cryptography.fernet import Fernet
ring = keyring.get_keyring()
res = ring.get_password("cloud_mdir_sync", "storage")
if res is None:
res = Fernet.generate_key()
ring.set_password("cloud_mdir_sync", "storage", res)
return res
def all_mboxes(self):
return itertools.chain(self.local_mboxes, self.cloud_mboxes)
def Office365_Account(self, user=None, tenant="common"):
"""Define an Office365 account credential. If user is left as None
then the browser will prompt for the user and the choice will be
cached. To lock the account to a single tenant specify the Azure
Directory name, ie 'contoso.onmicrosoft.com', or the GUID."""
from .office365 import GraphAPI
self.async_tasks.append(GraphAPI(self, user, tenant))
return self.async_tasks[-1]
def Office365(self, mailbox, account):
"""Create a cloud mailbox for Office365. Mailbox is the name of O365
mailbox to use, account should be the result of Office365_Account"""
from .office365 import O365Mailbox
self.cloud_mboxes.append(
O365Mailbox(self, mailbox, account))
return self.cloud_mboxes[-1]
def GMail_Account(self, user):
"""Define a GMail account credential. The user must be specified as a
fully qualified Google Account email address. This supports both
consumer GMail accounts, and accounts linked to a G-Suite account."""
from .gmail import GmailAPI
self.async_tasks.append(GmailAPI(self, user))
return self.async_tasks[-1]
def GMail(self, label, account):
"""Create a cloud mailbox for Office365. Mailbox is the name of O365
mailbox to use, account should be the result of Office365_Account"""
from .gmail import GMailMailbox
self.cloud_mboxes.append(GMailMailbox(self, label, account))
return self.cloud_mboxes[-1]
def MailDir(self, directory):
"""Create a local maildir to hold messages"""
from .maildir import MailDirMailbox
self.local_mboxes.append(MailDirMailbox(self, directory))
return self.local_mboxes[-1]
def CredentialServer(self,
path: str,
accounts: List,
umask=0o600,
protocols=["SMTP"]):
"""Serve XOAUTH2 bearer tokens over a unix domain socket. The client
writes the user to obtain a token for and the server responds with the
token. protocols can be IMAP or SMTP. The cms-oauth program interacts
with this server."""
from .credsrv import CredentialServer
self.async_tasks.append(
CredentialServer(self, path, accounts, umask, protocols))
return self.async_tasks[-1]
def _direct_message(self, msg):
return self.local_mboxes[0]
|