From 3ab022f45d868e383917d3b15530ecba7c6121e6 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Wed, 16 Nov 2016 11:38:03 -0500 Subject: [PATCH] add unstaged changes --- .../django/nntpchan/nntpchan/frontend/util.py | 40 ++- .../nntpchan/nntpchan/frontend/views.py | 19 +- .../nntpchan/templates/frontend/modlogin.html | 1 - .../nntpchan/templates/frontend/modpage.html | 9 +- .../django/nntpchan/nntpchan/views.py | 227 ++++++++++-------- 5 files changed, 181 insertions(+), 115 deletions(-) diff --git a/contrib/frontends/django/nntpchan/nntpchan/frontend/util.py b/contrib/frontends/django/nntpchan/nntpchan/frontend/util.py index f8008c7..b3e7b35 100644 --- a/contrib/frontends/django/nntpchan/nntpchan/frontend/util.py +++ b/contrib/frontends/django/nntpchan/nntpchan/frontend/util.py @@ -5,7 +5,7 @@ import hashlib import re import nacl.signing -from binascii import hexlify +from binascii import hexlify, unhexlify from datetime import datetime import time @@ -67,12 +67,20 @@ def createPost(newsgroup, ref, form, files, secretKey=None): msg['Date'] = email.utils.format_datetime(datetime.now()) if ref and not msgid_valid(ref): return None, "invalid reference: {}".format(ref) - if ref: - msg["References"] = ref msg["Newsgroups"] = newsgroup name = "Anonymous" if 'name' in form: 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"] = '{} '.format(name) if 'attachment' in files: msg['Content-Type'] = 'multipart/mixed' @@ -93,6 +101,10 @@ def createPost(newsgroup, ref, form, files, secretKey=None): m = '{}'.format(form['message'] or ' ') msg.set_payload(m) 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: msg['Path'] = settings.FRONTEND_NAME # sign @@ -103,7 +115,11 @@ def createPost(newsgroup, ref, form, files, secretKey=None): body = msg.as_bytes() h.update(body) 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: {} Content-Transfer-Encoding: 8bit Newsgroups: {} @@ -113,7 +129,7 @@ From: {} Date: {} 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') else: data = msg.as_bytes() @@ -131,3 +147,17 @@ Subject: {} if ref: return ref, 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 diff --git a/contrib/frontends/django/nntpchan/nntpchan/frontend/views.py b/contrib/frontends/django/nntpchan/nntpchan/frontend/views.py index 0c35a16..979323a 100644 --- a/contrib/frontends/django/nntpchan/nntpchan/frontend/views.py +++ b/contrib/frontends/django/nntpchan/nntpchan/frontend/views.py @@ -200,10 +200,23 @@ class ModView(generic.View, Postable): return render(request, 'frontend/redirect.html', {'url' : reverse('frontend:mod'), 'msg' : msg } ) else: # do mod action - return self.handle_mod_action(request) + return self.handle_mod_action(request, mod, action) - def handle_mod_action(self, request): - return render(request, 'frontend/redirect.html', {'url' : reverse('frontend:mod')} ) + def handle_mod_action(self, request, mod, action): + 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): mod = None diff --git a/contrib/frontends/django/nntpchan/nntpchan/templates/frontend/modlogin.html b/contrib/frontends/django/nntpchan/nntpchan/templates/frontend/modlogin.html index 2bb4925..a565f6f 100644 --- a/contrib/frontends/django/nntpchan/nntpchan/templates/frontend/modlogin.html +++ b/contrib/frontends/django/nntpchan/nntpchan/templates/frontend/modlogin.html @@ -7,5 +7,4 @@ - {% endblock %} diff --git a/contrib/frontends/django/nntpchan/nntpchan/templates/frontend/modpage.html b/contrib/frontends/django/nntpchan/nntpchan/templates/frontend/modpage.html index 53adc31..72b241c 100644 --- a/contrib/frontends/django/nntpchan/nntpchan/templates/frontend/modpage.html +++ b/contrib/frontends/django/nntpchan/nntpchan/templates/frontend/modpage.html @@ -5,6 +5,13 @@ -mod page +
+ {% csrf_token %} + + + + +
+ {% endblock %} diff --git a/contrib/frontends/django/nntpchan/nntpchan/views.py b/contrib/frontends/django/nntpchan/nntpchan/views.py index c1a2110..46e09b1 100644 --- a/contrib/frontends/django/nntpchan/nntpchan/views.py +++ b/contrib/frontends/django/nntpchan/nntpchan/views.py @@ -3,7 +3,7 @@ from django.http import HttpResponse, HttpResponseNotAllowed, JsonResponse from django.shortcuts import render 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 . import thumbnail @@ -28,112 +28,9 @@ def webhook(request): """ if request.method != 'POST': return HttpResponseNotAllowed(['POST']) - try: msg = email.message_from_bytes(request.body) - newsgroup = msg.get('Newsgroups') - - if newsgroup is None: - raise Exception("no newsgroup specified") - - if not util.newsgroup_valid(newsgroup): - raise Exception("invalid newsgroup name") - - bump = True - group, created = Newsgroup.objects.get_or_create(name=newsgroup) - if created: - group.save() - if group.banned: - raise Exception("newsgroup is banned") - - msgid = None - for h in ('Message-ID', 'Message-Id', 'MessageId', 'MessageID'): - if h in msg: - msgid = msg[h] - break - # check for sage - if 'X-Sage' in msg and msg['X-Sage'] == '1': - bump = False - - if msgid is None: - raise Exception("no message id specified") - elif not util.msgid_valid(msgid): - raise Exception("invalid message id format: {}".format(msgid)) - - opmsgid = msgid - - h = util.hashid(msgid) - atts = list() - ref = msg['References'] or '' - posted = util.time_int(email.utils.parsedate_to_datetime(msg['Date'])) - - if len(ref) > 0: - opmsgid = ref - - f = msg['From'] or 'anon ' - name = email.utils.parseaddr(f)[0] - post, created = Post.objects.get_or_create(defaults={ - 'posthash': h, - 'reference': ref, - 'posted': posted, - 'last_bumped': 0, - 'name': name, - 'subject': msg["Subject"] or '', - 'newsgroup': group}, msgid=msgid) - if not created: - post.subject = msg["Subject"] or '' - post.name = name - post.posted = posted - m = '' - - for part in msg.walk(): - ctype = part.get_content_type() - if ctype.startswith("text/plain"): - m += '{} '.format(part.get_payload(decode=True).decode('utf-8')) - else: - payload = part.get_payload(decode=True) - if payload is None: - continue - filename = part.get_filename() - mtype = part.get_content_type() - ext = filename.split('.')[-1].lower() - fh = util.hashfile(bytes(payload)) - fn = fh + '.' + ext - fname = os.path.join(settings.MEDIA_ROOT, fn) - if not os.path.exists(fname): - with open(fname, 'wb') as f: - f.write(payload) - tname = os.path.join(settings.MEDIA_ROOT, 'thumb-{}.jpg'.format(fn)) - placeholder = os.path.join(settings.ASSETS_ROOT, 'placeholder.jpg') - if not os.path.exists(tname): - thumbnail.generate(fname, tname, placeholder) - - att = Attachment(filehash=fh) - att.mimetype = mtype - att.filename = filename - att.save() - atts.append(att) - post.message = m - post.save() - - for att in atts: - if post.has_attachment(att.filehash): - continue - post.attachments.add(att) - - - op, _ = Post.objects.get_or_create(defaults={ - 'posthash': util.hashid(opmsgid), - 'reference': '', - 'posted': 0, - 'last_bumped': 0, - 'name': 'OP', - 'subject': 'OP Not Found', - 'newsgroup': group}, msgid=opmsgid) - if bump: - op.bump(post.posted) - op.save() - + process_message(msg) except Exception as ex: traceback.print_exc() return JsonResponse({ 'error': '{}'.format(ex) }) @@ -141,3 +38,123 @@ def webhook(request): return JsonResponse({'posted': True}) + + +def process_message(msg): + + newsgroup = msg.get('Newsgroups') + + if newsgroup is None: + raise Exception("no newsgroup specified") + + if not util.newsgroup_valid(newsgroup): + raise Exception("invalid newsgroup name") + + bump = True + group, created = Newsgroup.objects.get_or_create(name=newsgroup) + if created: + group.save() + if group.banned: + raise Exception("newsgroup is banned") + + msgid = None + for h in ('Message-ID', 'Message-Id', 'MessageId', 'MessageID'): + if h in msg: + msgid = msg[h] + break + # check for sage + if 'X-Sage' in msg and msg['X-Sage'] == '1': + bump = False + + if msgid is None: + raise Exception("no message id specified") + elif not util.msgid_valid(msgid): + raise Exception("invalid message id format: {}".format(msgid)) + + opmsgid = msgid + + h = util.hashid(msgid) + atts = list() + ref = msg['References'] or '' + posted = util.time_int(email.utils.parsedate_to_datetime(msg['Date'])) + + if len(ref) > 0: + opmsgid = ref + + f = msg['From'] or 'anon ' + name = email.utils.parseaddr(f)[0] + post, created = Post.objects.get_or_create(defaults={ + 'posthash': h, + 'reference': ref, + 'posted': posted, + 'last_bumped': 0, + 'name': name, + 'subject': msg["Subject"] or '', + 'newsgroup': group}, msgid=msgid) + + if not created: + post.subject = msg["Subject"] or '' + post.name = name + post.posted = posted + m = '' + + for part in msg.walk(): + ctype = part.get_content_type() + print (ctype) + if ctype.startswith("text/plain"): + 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: + payload = part.get_payload(decode=True) + if payload is None: + continue + filename = part.get_filename() + mtype = part.get_content_type() + ext = filename.split('.')[-1].lower() + fh = util.hashfile(bytes(payload)) + fn = fh + '.' + ext + fname = os.path.join(settings.MEDIA_ROOT, fn) + if not os.path.exists(fname): + with open(fname, 'wb') as f: + f.write(payload) + tname = os.path.join(settings.MEDIA_ROOT, 'thumb-{}.jpg'.format(fn)) + placeholder = os.path.join(settings.ASSETS_ROOT, 'placeholder.jpg') + if not os.path.exists(tname): + thumbnail.generate(fname, tname, placeholder) + + att = Attachment(filehash=fh) + att.mimetype = mtype + att.filename = filename + att.save() + atts.append(att) + post.message = m + post.save() + + + for att in atts: + if post.has_attachment(att.filehash): + continue + post.attachments.add(att) + + + op, _ = Post.objects.get_or_create(defaults={ + 'posthash': util.hashid(opmsgid), + 'reference': '', + 'posted': 0, + 'last_bumped': 0, + 'name': 'OP', + 'subject': 'OP Not Found', + 'newsgroup': group}, msgid=opmsgid) + if bump: + op.bump(post.posted) + op.save() +