52
|
52
|
|
message_raw = sys.stdin.read()
|
53
|
53
|
|
message = email.message_from_string(message_raw, policy=email.policy.default)
|
54
|
54
|
|
|
55
|
|
- |
email_id = message.get('message-id')
|
56
|
|
- |
email_id_hash = hashlib.sha256(email_id.encode('utf-8')).hexdigest()[:8] # This will be used as thread ID
|
57
|
|
- |
email_from = email.utils.parseaddr(message.get('from'))
|
58
|
|
- |
email_to = email.utils.parseaddr(message.get('to'))
|
59
|
|
- |
email_in_reply_to = message.get('in-reply-to')
|
60
|
|
- |
email_subject = message.get('subject')
|
61
|
|
- |
email_body = message.get_body(('plain',)).get_content() # Accept plaintext only!
|
|
55
|
+ |
try:
|
|
56
|
+ |
email_id = message.get('message-id').strip()
|
|
57
|
+ |
except:
|
|
58
|
+ |
logging.error('Refuting email without a Message-ID: {}'.format(email_subject))
|
|
59
|
+ |
exit()
|
62
|
60
|
|
|
63
|
|
- |
logging.info('Received email from {} to {} with subject {}'.format(email_from, email_to, email_subject))
|
|
61
|
+ |
email_id_hash = hashlib.sha256(email_id.encode('utf-8')).hexdigest()[:8] # This will be used as thread ID
|
64
|
62
|
|
|
65
|
|
- |
if not email_id:
|
66
|
|
- |
logging.info('Refuting email without a Message-ID: {}'.format(email_subject))
|
|
63
|
+ |
try:
|
|
64
|
+ |
email_from = email.utils.parseaddr(message.get('from'))
|
|
65
|
+ |
assert len(email_from[1]) > 0
|
|
66
|
+ |
except:
|
|
67
|
+ |
logging.error('Refuting email with From header: {}'.format(email_from))
|
67
|
68
|
|
exit()
|
68
|
69
|
|
|
69
|
|
- |
if not email_body:
|
70
|
|
- |
logging.warning('Refuting email without plaintext body: {}'.format(email_subject))
|
|
70
|
+ |
try:
|
|
71
|
+ |
email_to = email.utils.parseaddr(message.get('to'))
|
|
72
|
+ |
assert len(email_from[1]) > 0
|
|
73
|
+ |
assert email_to[1].endswith('@' + SERVER_DOMAIN)
|
|
74
|
+ |
except:
|
|
75
|
+ |
logging.error('Refuting email with To header: {}'.format(email_to))
|
71
|
76
|
|
exit()
|
72
|
77
|
|
|
73
|
|
- |
if not email_to[1].endswith('@' + SERVER_DOMAIN):
|
74
|
|
- |
logging.warning('Refuting email with bad recipient domain: {}'.format(email_to))
|
75
|
|
- |
exit()
|
|
78
|
+ |
email_in_reply_to = message.get('in-reply-to')
|
|
79
|
+ |
if email_in_reply_to:
|
|
80
|
+ |
email_in_reply_to = email_in_reply_to.strip()
|
|
81
|
+ |
|
|
82
|
+ |
try:
|
|
83
|
+ |
email_subject = message.get('subject').strip()
|
|
84
|
+ |
except:
|
|
85
|
+ |
email_subject = ''
|
|
86
|
+ |
|
|
87
|
+ |
try:
|
|
88
|
+ |
# Accept plaintext only!
|
|
89
|
+ |
email_body = message.get_body(('plain',)).get_content()
|
|
90
|
+ |
except:
|
|
91
|
+ |
email_body = ''
|
|
92
|
+ |
|
|
93
|
+ |
logging.info('Received email from {} to {} with subject "{}"'.format(email_from, email_to, email_subject))
|
76
|
94
|
|
|
77
|
95
|
|
# Get the repository name. We use email addresses formatted as <repository>@SERVER_DOMAIN
|
78
|
96
|
|
repository_name = email_to[1].rsplit('@', 1)[0]
|
|
97
|
+ |
|
|
98
|
+ |
# Is this a request for subscription?
|
|
99
|
+ |
request_subscribe = repository_name.endswith('+subscribe')
|
|
100
|
+ |
request_unsubscribe = repository_name.endswith('+unsubscribe')
|
|
101
|
+ |
|
|
102
|
+ |
# Remove leading command: from address
|
|
103
|
+ |
if request_subscribe: repository_name = repository_name[:-10]
|
|
104
|
+ |
if request_unsubscribe: repository_name = repository_name[:-12]
|
|
105
|
+ |
|
79
|
106
|
|
repository_path = os.path.join(REPOSITORIES_PATH, repository_name + '.mlist.git')
|
80
|
107
|
|
|
81
|
108
|
|
if '..' in repository_name:
|
82
|
|
- |
logging.warning('Refuting email because the repository name contains "..": {}'.format(repository_name))
|
|
109
|
+ |
logging.error('Refuting email because the repository name contains "..": {}'.format(repository_name))
|
83
|
110
|
|
exit()
|
84
|
111
|
|
|
|
112
|
+ |
# All repositories should be <username>/<reponame>
|
85
|
113
|
|
if '/' not in repository_name:
|
86
|
|
- |
logging.warning('Refuting email because the repository name does not contain a namespace: {}'.format(repository_name))
|
|
114
|
+ |
logging.error('Refuting email because the repository name does not contain a namespace: {}'.format(repository_name))
|
87
|
115
|
|
exit()
|
88
|
116
|
|
|
89
|
|
- |
|
90
|
|
- |
|
91
|
|
- |
|
92
|
|
- |
###############################################################################
|
93
|
|
- |
# ADD EMAIL TO USER REPOSITORY
|
94
|
|
- |
###############################################################################
|
95
|
|
- |
|
96
|
117
|
|
if not os.path.isdir(repository_path):
|
97
|
|
- |
logging.warning('Repository path does not exist: {}'.format(repository_path))
|
|
118
|
+ |
logging.error('Repository path does not exist: {}'.format(repository_path))
|
98
|
119
|
|
exit()
|
99
|
120
|
|
|
100
|
121
|
|
try:
|
101
|
122
|
|
repo = pygit2.Repository(repository_path)
|
102
|
123
|
|
except:
|
103
|
|
- |
logging.warning('Not a valid repository: {}'.format(repository_path))
|
|
124
|
+ |
logging.error('Not a valid repository: {}'.format(repository_path))
|
104
|
125
|
|
exit()
|
105
|
126
|
|
|
106
|
127
|
|
try:
|
107
|
128
|
|
head_tree = repo.revparse_single('HEAD').tree
|
108
|
129
|
|
except:
|
109
|
|
- |
logging.warning('Could not find HEAD ref: {}'.format(repository_path))
|
|
130
|
+ |
logging.error('Could not find HEAD ref: {}'.format(repository_path))
|
110
|
131
|
|
exit()
|
111
|
132
|
|
|
112
|
133
|
|
try:
|
117
|
138
|
|
subscribers.append(addr)
|
118
|
139
|
|
except:
|
119
|
140
|
|
subscribers = []
|
120
|
|
- |
logging.info('Could not load subscribers file: {}'.format(repository_path))
|
|
141
|
+ |
logging.info('Subscribers file not found in {}'.format(repository_path))
|
|
142
|
+ |
|
|
143
|
+ |
|
|
144
|
+ |
|
|
145
|
+ |
|
|
146
|
+ |
###############################################################################
|
|
147
|
+ |
# LISTS SUBSCRIPTION
|
|
148
|
+ |
###############################################################################
|
|
149
|
+ |
|
|
150
|
+ |
if request_subscribe \
|
|
151
|
+ |
and email_from[1] in subscribers:
|
|
152
|
+ |
# Already subscribed
|
|
153
|
+ |
exit()
|
|
154
|
+ |
|
|
155
|
+ |
if request_unsubscribe \
|
|
156
|
+ |
and email_from[1] not in subscribers:
|
|
157
|
+ |
# No address to removed
|
|
158
|
+ |
exit()
|
|
159
|
+ |
|
|
160
|
+ |
if request_subscribe or request_unsubscribe:
|
|
161
|
+ |
if request_subscribe:
|
|
162
|
+ |
subscribers.append(email_from[1])
|
|
163
|
+ |
commit_message = 'Subscribe'
|
|
164
|
+ |
|
|
165
|
+ |
if request_unsubscribe:
|
|
166
|
+ |
subscribers = [ address for address in subscribers if address != email_from[1] ]
|
|
167
|
+ |
commit_message = 'Unsubscribe'
|
|
168
|
+ |
|
|
169
|
+ |
# Add a new BLOB to the git store
|
|
170
|
+ |
oid = repo.create_blob('\n'.join(subscribers).encode('UTF-8'))
|
|
171
|
+ |
|
|
172
|
+ |
# Add the blob that we've just created to the HEAD tree
|
|
173
|
+ |
head_tree_builder = repo.TreeBuilder(head_tree)
|
|
174
|
+ |
head_tree_builder.insert('subscribers', oid, pygit2.GIT_FILEMODE_BLOB)
|
|
175
|
+ |
head_tree_oid = head_tree_builder.write()
|
|
176
|
+ |
|
|
177
|
+ |
repo.create_commit(
|
|
178
|
+ |
repo.head.name, # reference name
|
|
179
|
+ |
pygit2.Signature('CLIF', '-'), # author
|
|
180
|
+ |
pygit2.Signature('CLIF', '-'), # committer
|
|
181
|
+ |
commit_message, # message
|
|
182
|
+ |
head_tree_oid, # tree of this commit
|
|
183
|
+ |
[ repo.head.target ] # parents commit
|
|
184
|
+ |
)
|
|
185
|
+ |
|
|
186
|
+ |
exit()
|
|
187
|
+ |
|
|
188
|
+ |
|
|
189
|
+ |
|
|
190
|
+ |
|
|
191
|
+ |
###############################################################################
|
|
192
|
+ |
# ADD EMAIL TO USER REPOSITORY
|
|
193
|
+ |
###############################################################################
|
|
194
|
+ |
|
|
195
|
+ |
if len(email_subject) == 0:
|
|
196
|
+ |
logging.info('Refuting email with no subject: {}'.format(email_id))
|
|
197
|
+ |
exit()
|
|
198
|
+ |
|
|
199
|
+ |
if not email_body:
|
|
200
|
+ |
logging.warning('Refuting email without plaintext body: {}'.format(email_subject))
|
|
201
|
+ |
exit()
|
|
202
|
+ |
|
|
203
|
+ |
if len(email_body.strip()) == 0:
|
|
204
|
+ |
logging.info('Refuting email with empty body: {}'.format(email_id))
|
|
205
|
+ |
exit()
|
121
|
206
|
|
|
122
|
207
|
|
logging.debug('Accepting email from {} to {} with subject {}'.format(email_from, email_to, email_subject))
|
123
|
208
|
|
|