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,112 +28,9 @@ 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)
newsgroup = msg.get('Newsgroups') process_message(msg)
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 <anon@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()
except Exception as ex: except Exception as ex:
traceback.print_exc() traceback.print_exc()
return JsonResponse({ 'error': '{}'.format(ex) }) return JsonResponse({ 'error': '{}'.format(ex) })
@ -141,3 +38,123 @@ def webhook(request):
return JsonResponse({'posted': True}) 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 <anon@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()