Archived
1
0

add unstaged changes

This commit is contained in:
Jeff Becker 2016-11-16 11:38:03 -05:00
parent c677cebde5
commit 3ab022f45d
5 changed files with 181 additions and 115 deletions

View File

@ -5,7 +5,7 @@ import hashlib
import re import re
import nacl.signing import nacl.signing
from binascii import hexlify from binascii import hexlify, unhexlify
from datetime import datetime from datetime import datetime
import time import time
@ -67,12 +67,20 @@ def createPost(newsgroup, ref, form, files, secretKey=None):
msg['Date'] = email.utils.format_datetime(datetime.now()) msg['Date'] = email.utils.format_datetime(datetime.now())
if ref and not msgid_valid(ref): if ref and not msgid_valid(ref):
return None, "invalid reference: {}".format(ref) return None, "invalid reference: {}".format(ref)
if ref:
msg["References"] = ref
msg["Newsgroups"] = newsgroup msg["Newsgroups"] = newsgroup
name = "Anonymous" name = "Anonymous"
if 'name' in form: if 'name' in form:
name = form['name'] or name name = form['name'] or name
if '#' in name:
parts = name.split('#')
secret = name[1+len(name):]
name = parts[0]
try:
assert len(unhexlify(secret.encode('ascii'))) == 32
except:
secret = hashlib.sha256(secret.encode('utf-8')).hexdigest()
secretKey = secret
msg["From"] = '{} <anon@django.nntpchan.tld>'.format(name) msg["From"] = '{} <anon@django.nntpchan.tld>'.format(name)
if 'attachment' in files: if 'attachment' in files:
msg['Content-Type'] = 'multipart/mixed' msg['Content-Type'] = 'multipart/mixed'
@ -93,6 +101,10 @@ def createPost(newsgroup, ref, form, files, secretKey=None):
m = '{}'.format(form['message'] or ' ') m = '{}'.format(form['message'] or ' ')
msg.set_payload(m) msg.set_payload(m)
msg['Message-Id'] = '<{}${}@{}>'.format(randstr(5), int(time_int(datetime.now())), settings.FRONTEND_NAME) msg['Message-Id'] = '<{}${}@{}>'.format(randstr(5), int(time_int(datetime.now())), settings.FRONTEND_NAME)
if ref:
msg["References"] = ref
else:
msg["References"] = msg["Message-Id"]
if secretKey: if secretKey:
msg['Path'] = settings.FRONTEND_NAME msg['Path'] = settings.FRONTEND_NAME
# sign # sign
@ -103,7 +115,11 @@ def createPost(newsgroup, ref, form, files, secretKey=None):
body = msg.as_bytes() body = msg.as_bytes()
h.update(body) h.update(body)
sig = hexlify(keypair.sign(h.digest()).signature).decode('ascii') sig = hexlify(keypair.sign(h.digest()).signature).decode('ascii')
data = '''Content-Type: message/rfc822; charset=UTF-8 if ref:
data = 'References: ' + ref + '\n'
else:
data = ''
data += '''Content-Type: message/rfc822; charset=UTF-8
Message-ID: {} Message-ID: {}
Content-Transfer-Encoding: 8bit Content-Transfer-Encoding: 8bit
Newsgroups: {} Newsgroups: {}
@ -113,7 +129,7 @@ From: {}
Date: {} Date: {}
Subject: {} Subject: {}
{}'''.format(msg["Message-ID"], newsgroup, pubkey, sig, msg["From"], msg["Date"], msg['Subject'], msg.as_string()) {}\n'''.format(msg["Message-ID"], msg["Refereces"], newsgroup, pubkey, sig, msg["From"], msg["Date"], msg['Subject'], msg.as_string())
data = data.encode('utf-8') data = data.encode('utf-8')
else: else:
data = msg.as_bytes() data = msg.as_bytes()
@ -131,3 +147,17 @@ Subject: {}
if ref: if ref:
return ref, None return ref, None
return None, None return None, None
def verify_message(pubkey, sig, payload):
h = hashlib.sha512()
h.update(payload[:-1])
d = h.digest()
sig = unhexlify(sig)
k = nacl.signing.VerifyKey(pubkey, nacl.signing.encoding.HexEncoder)
try:
k.verify(d, sig)
except:
return False
else:
return True

View File

@ -200,10 +200,23 @@ class ModView(generic.View, Postable):
return render(request, 'frontend/redirect.html', {'url' : reverse('frontend:mod'), 'msg' : msg } ) return render(request, 'frontend/redirect.html', {'url' : reverse('frontend:mod'), 'msg' : msg } )
else: else:
# do mod action # do mod action
return self.handle_mod_action(request) return self.handle_mod_action(request, mod, action)
def handle_mod_action(self, request): def handle_mod_action(self, request, mod, action):
return render(request, 'frontend/redirect.html', {'url' : reverse('frontend:mod')} ) msg = 'no action made'
if action is not None:
if action == 'delete':
# handle bulk delete
if 'posts' in request.POST:
body = '\ndelete '.join(request.POST['posts'].split())
sk = mod['sk']
_, err = util.createPost('ctl', '', {'message' : body }, {}, sk)
if err:
msg = 'error: {}'.format(err)
else:
msg = 'okay'
return render(request, 'frontend/redirect.html', {'url' : reverse('frontend:mod'), 'msg' : msg} )
def get(self, request): def get(self, request):
mod = None mod = None

View File

@ -7,5 +7,4 @@
<input type="hidden" name="action" value="login"></input> <input type="hidden" name="action" value="login"></input>
<input type="submit" value="login"></input> <input type="submit" value="login"></input>
</form> </form>
{% endblock %} {% endblock %}

View File

@ -5,6 +5,13 @@
<input type="hidden" name="action" value="logout"></input> <input type="hidden" name="action" value="logout"></input>
<input type="submit" value="logout"></input> <input type="submit" value="logout"></input>
</form> </form>
mod page <form method="POST">
{% csrf_token %}
<input type="hidden" name="action" value="delete"></input>
<label for="bulk-delete"> bulk delete</label>
<textarea name="posts"></textarea>
<input type="submit" value="delete"></input>
</form>
{% endblock %} {% endblock %}

View File

@ -3,7 +3,7 @@ from django.http import HttpResponse, HttpResponseNotAllowed, JsonResponse
from django.shortcuts import render from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from .frontend.models import Post, Attachment, Newsgroup from .frontend.models import Post, Attachment, Newsgroup, ModPriv
from .frontend import util from .frontend import util
from . import thumbnail from . import thumbnail
@ -28,9 +28,20 @@ def webhook(request):
""" """
if request.method != 'POST': if request.method != 'POST':
return HttpResponseNotAllowed(['POST']) return HttpResponseNotAllowed(['POST'])
try: try:
msg = email.message_from_bytes(request.body) msg = email.message_from_bytes(request.body)
process_message(msg)
except Exception as ex:
traceback.print_exc()
return JsonResponse({ 'error': '{}'.format(ex) })
else:
return JsonResponse({'posted': True})
def process_message(msg):
newsgroup = msg.get('Newsgroups') newsgroup = msg.get('Newsgroups')
if newsgroup is None: if newsgroup is None:
@ -80,6 +91,7 @@ def webhook(request):
'name': name, 'name': name,
'subject': msg["Subject"] or '', 'subject': msg["Subject"] or '',
'newsgroup': group}, msgid=msgid) 'newsgroup': group}, msgid=msgid)
if not created: if not created:
post.subject = msg["Subject"] or '' post.subject = msg["Subject"] or ''
post.name = name post.name = name
@ -88,8 +100,19 @@ def webhook(request):
for part in msg.walk(): for part in msg.walk():
ctype = part.get_content_type() ctype = part.get_content_type()
print (ctype)
if ctype.startswith("text/plain"): if ctype.startswith("text/plain"):
m += '{} '.format(part.get_payload(decode=True).decode('utf-8')) m += '{} '.format(part.get_payload(decode=True).decode('utf-8'))
elif ctype.startswith("message/rfc822"):
# signed message
payload = part.get_payload()
if payload is None:
raise Exception('invalid signed message, no body')
for inner in payload:
if not util.verify_message(msg["X-Pubkey-Ed25519"], msg['X-Signature-Ed25519-Sha512'], inner.as_bytes()):
raise Exception('invalid signed message, signature failed')
process_message(inner)
print('processed inner')
else: else:
payload = part.get_payload(decode=True) payload = part.get_payload(decode=True)
if payload is None: if payload is None:
@ -116,6 +139,7 @@ def webhook(request):
post.message = m post.message = m
post.save() post.save()
for att in atts: for att in atts:
if post.has_attachment(att.filehash): if post.has_attachment(att.filehash):
continue continue
@ -134,10 +158,3 @@ def webhook(request):
op.bump(post.posted) op.bump(post.posted)
op.save() op.save()
except Exception as ex:
traceback.print_exc()
return JsonResponse({ 'error': '{}'.format(ex) })
else:
return JsonResponse({'posted': True})