From fa369d636fba95d58b01674de0614b6623ec00a0 Mon Sep 17 00:00:00 2001 From: kalzu rekku Date: Sun, 28 Jul 2024 11:12:44 +0300 Subject: [PATCH] Now we have working paste sending to 4 places?. PrivateBin client is functional, we can use any PrivateBin with it! --- .gitignore | 2 + cpaste.py | 423 +++++++++++++++++++++++++++++++++++++++++++++++++++++ mpaste.py | 90 +++++++----- test.txt | 1 - 4 files changed, 477 insertions(+), 39 deletions(-) create mode 100644 .gitignore create mode 100644 cpaste.py delete mode 100644 test.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1164261 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +*.txt diff --git a/cpaste.py b/cpaste.py new file mode 100644 index 0000000..9c6b81e --- /dev/null +++ b/cpaste.py @@ -0,0 +1,423 @@ +#!/usr/bin/env python3 +# +####################################################################### +# +# A script to paste to https://cpaste.org/ +# +# Copyright (c) 2013-2019 Andreas Schneider +# Copyright (c) 2013 Alexander Bokovoy +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +####################################################################### +# +# Requires: python3-requests +# Requires: python3-cryptography +# +# Optionally requires: python-Pygments +# + +import os +import sys +import json +import base64 +import zlib +import requests +from cryptography.hazmat.primitives.ciphers.aead import AESGCM +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC +from cryptography.hazmat.backends import default_backend +from optparse import OptionParser +from mimetypes import guess_type +try: + from pygments.lexers import guess_lexer, guess_lexer_for_filename + from pygments.util import ClassNotFound + guess_lang = True +except ImportError: + guess_lang = False + + +def base58_encode(v: bytes): + # 58 char alphabet + alphabet = b'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' + alphabet_len = len(alphabet) + + nPad = len(v) + v = v.lstrip(b'\0') + nPad -= len(v) + + x = 0 + for (i, c) in enumerate(v[::-1]): + if isinstance(c, str): + c = ord(c) + x += c << (8 * i) + + string = b'' + while x: + x, idx = divmod(x, alphabet_len) + string = alphabet[idx:idx+1] + string + + return (alphabet[0:1] * nPad + string) + + +def json_encode(d): + return json.dumps(d, separators=(',', ':')).encode('utf-8') + + +# +# The encryption format is described here: +# https://github.com/PrivateBin/PrivateBin/wiki/Encryption-format +# +def privatebin_encrypt(paste_passphrase, + paste_password, + paste_plaintext, + paste_formatter, + paste_attachment_name, + paste_attachment, + paste_compress, + paste_burn, + paste_opendicussion): + if paste_password: + paste_passphrase += bytes(paste_password, 'utf-8') + + # PBKDF + kdf_salt = bytes(os.urandom(8)) + kdf_iterations = 100000 + kdf_keysize = 256 # size of resulting kdf_key + + backend = default_backend() + kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), + length=int(kdf_keysize / 8), # 256bit + salt=kdf_salt, + iterations=kdf_iterations, + backend=backend) + kdf_key = kdf.derive(paste_passphrase) + + # AES-GCM + adata_size = 128 + + cipher_iv = bytes(os.urandom(int(adata_size / 8))) + cipher_algo = "aes" + cipher_mode = "gcm" + + compression_type = "none" + if paste_compress: + compression_type = "zlib" + + # compress plaintext + paste_data = {'paste': paste_plaintext} + if paste_attachment_name and paste_attachment: + paste_data['attachment'] = paste_attachment + paste_data['attachment_name'] = paste_attachment_name + print(paste_attachment_name) + print(paste_attachment) + + if paste_compress: + zobj = zlib.compressobj(wbits=-zlib.MAX_WBITS) + paste_blob = zobj.compress(json_encode(paste_data)) + zobj.flush() + else: + paste_blob = json_encode(paste_data) + + # Associated data to authenticate + paste_adata = [ + [ + base64.b64encode(cipher_iv).decode("utf-8"), + base64.b64encode(kdf_salt).decode("utf-8"), + kdf_iterations, + kdf_keysize, + adata_size, + cipher_algo, + cipher_mode, + compression_type, + ], + paste_formatter, + int(paste_opendicussion), + int(paste_burn), + ] + + paste_adata_json = json_encode(paste_adata) + + aesgcm = AESGCM(kdf_key) + ciphertext = aesgcm.encrypt(cipher_iv, paste_blob, paste_adata_json) + + # Validate + # aesgcm.decrypt(cipher_iv, ciphertext, paste_adata_json) + + paste_ciphertext = base64.b64encode(ciphertext).decode("utf-8") + + return paste_adata, paste_ciphertext + + +def privatebin_send(paste_url, + paste_password, + paste_plaintext, + paste_formatter, + paste_attachment_name, + paste_attachment, + paste_compress, + paste_burn, + paste_opendicussion, + paste_expire): + paste_passphrase = bytes(os.urandom(32)) + + paste_adata, paste_ciphertext = privatebin_encrypt(paste_passphrase, + paste_password, + paste_plaintext, + paste_formatter, + paste_attachment_name, + paste_attachment, + paste_compress, + paste_burn, + paste_opendicussion) + + # json payload for the post API + # https://github.com/PrivateBin/PrivateBin/wiki/API + payload = { + "v": 2, + "adata": paste_adata, + "ct": paste_ciphertext, + "meta": { + "expire": paste_expire, + } + } + + # http content type + headers = {'X-Requested-With': 'JSONHttpRequest'} + + r = requests.post(paste_url, + data=json_encode(payload), + headers=headers) + r.raise_for_status() + + try: + result = r.json() + except: + print('Oops, error: %s' % (r.text)) + sys.exit(1) + + paste_status = result['status'] + if paste_status: + paste_message = result['message'] + print("Oops, error: %s" % paste_message) + sys.exit(1) + + paste_id = result['id'] + paste_url_id = result['url'] + paste_deletetoken = result['deletetoken'] + + print('Delete paste: %s/?pasteid=%s&deletetoken=%s' % + (paste_url, paste_id, paste_deletetoken)) +# print('') +# print('### Paste (%s): %s%s#%s' % +# (paste_formatter, +# paste_url, +# paste_url_id, +# base58_encode(paste_passphrase).decode('utf-8'))) + + return paste_url, paste_url_id, base58_encode(paste_passphrase).decode('utf-8'), paste_deletetoken + + + +def guess_lang_formatter(paste_plaintext, paste_filename=None): + paste_formatter = 'plaintext' + + # Map numpy to python because the numpy lexer gives false positives + # when guessing. + lexer_lang_map = {'numpy': 'python'} + + # If we have a filename, try guessing using the more reliable + # guess_lexer_for_filename function. + # If that fails, try the guess_lexer function on the code. + lang = None + if paste_filename: + try: + lang = guess_lexer_for_filename(paste_filename, + paste_plaintext).name.lower() + except ClassNotFound: + print("No guess by filename") + pass + else: + try: + lang = guess_lexer(paste_plaintext).name.lower() + except ClassNotFound: + pass + + if lang: + if lang == 'markdown': + paste_formatter = 'markdown' + if lang != 'text only': + paste_formatter = 'syntaxhighlighting' + + return paste_formatter + + +def main(): + parser = OptionParser() + + parser.add_option("-f", "--file", dest="filename", + help="Read from a file instead of stdin", + metavar="FILE") + parser.add_option("-p", "--password", dest="password", + help="Create a password protected paste", + metavar="PASSWORD") + parser.add_option("-e", "--expire", + action="store", dest="expire", default="1day", + choices=["5min", + "10min", + "1hour", + "1day", + "1week", + "1month", + "1year", + "never"], + help="Expiration time of the paste (default: 1day)") + parser.add_option("-s", "--sourcecode", + action="store_true", dest="source", default=False, + help="Use source code highlighting") + parser.add_option("-m", "--markdown", + action="store_true", dest="markdown", default=False, + help="Parse paste as markdown") + parser.add_option("-b", "--burn", + action="store_true", dest="burn", default=False, + help="Burn paste after reading") + parser.add_option("-o", "--opendiscussion", + action="store_true", dest="opendiscussion", + default=False, + help="Allow discussion for the paste") + parser.add_option("-a", "--attachment", dest="attachment", + help="Specify path to a file to attachment to the paste", + metavar="FILE") + + (options, args) = parser.parse_args() + + paste_url = 'https://cpaste.org' + paste_formatter = 'plaintext' + paste_compress = True + paste_expire = '1day' + paste_opendiscussion = 0 + paste_burn = 0 + paste_password = None + paste_attachment_name = None + paste_attachment = None + + if options.filename: + f = open(options.filename) + if not f: + print("Oops, could not open file!") + + paste_plaintext = f.read() + f.close() + else: + paste_plaintext = sys.stdin.read() + + if not paste_plaintext: + print("Oops, we have no data") + sys.exit(1) + + if options.burn: + paste_burn = 1 + + if options.opendiscussion: + paste_opendiscussion = 1 + + if options.source: + paste_formatter = 'syntaxhighlighting' + elif options.markdown: + paste_formatter = 'markdown' + elif guess_lang: + paste_formatter = guess_lang_formatter(paste_plaintext, + options.filename) + + if options.expire: + paste_expire = options.expire + + if options.password: + paste_password = options.password + + if options.attachment: + paste_attachment_name = os.path.basename(options.attachment) + mime = guess_type(options.attachment, strict=False)[0] + if not mime: + mime = 'application/octet-stream' + + f = open(options.attachment, mode='rb') + if not f: + print("Oops, could not open file for attachment!") + + data = f.read() + f.close() + + paste_attachment = 'data:%s;base64,' % (mime) + paste_attachment += base64.b64encode(data).decode('utf-8') + + print(paste_url) + print(paste_password ) + print(paste_plaintext ) + print(paste_formatter ) + print(paste_attachment_name ) + print(paste_attachment ) + print(paste_compress ) + print(paste_burn ) + print(paste_opendiscussion ) + print(paste_expire ) + + privatebin_send(paste_url, + paste_password, + paste_plaintext, + paste_formatter, + paste_attachment_name, + paste_attachment, + paste_compress, + paste_burn, + paste_opendiscussion, + paste_expire) + + sys.exit(0) + +def publish_to_cpaste(file): + #paste_url = 'https://cpaste.org' + paste_url = 'https://paste.devsite.pl' + paste_formatter = 'plaintext' + paste_compress = True + paste_expire = '1day' + paste_opendiscussion = 0 + paste_burn = 0 + paste_password = None + paste_attachment_name = None + paste_attachment = None + + f = open(file) + if not f: + print("Oops, could not open file!") + + paste_plaintext = f.read() + f.close() + + paste_url, paste_id, paste_decrypt, paste_deletetoken = privatebin_send(paste_url, + paste_password, + paste_plaintext, + paste_formatter, + paste_attachment_name, + paste_attachment, + paste_compress, + paste_burn, + paste_opendiscussion, + paste_expire) + + output = '%s%s#%s DELETE TOKEN: %s' % (paste_url, paste_id, paste_decrypt, paste_deletetoken) + return output + + +if __name__ == "__main__": + main() diff --git a/mpaste.py b/mpaste.py index 579b4c8..7206b57 100644 --- a/mpaste.py +++ b/mpaste.py @@ -1,5 +1,11 @@ import requests +import secrets +import base64 +import tempfile import socket +import os + +from cpaste import publish_to_cpaste def publish_to_termbin(content): host = 'termbin.com' @@ -25,18 +31,8 @@ def publish_to_mozilla(content): } response = requests.post(url, data=data) if response.status_code == 200: - return f"https://pastebin.mozilla.org/{response.text}" - return None - -def publish_to_paste2(content): - url = 'https://paste2.org/api/create' - data = { - 'content': content, - 'lang': 'text' - } - response = requests.post(url, data=data) - if response.status_code == 200: - return f"https://paste2.org/{response.text}" + # Mozilla returns full link to the paste + return f"{response.text}" return None def publish_to_dpaste(content): @@ -50,21 +46,46 @@ def publish_to_dpaste(content): return response.text.strip() return None -def publish_to_pasteee(content, api_key): - url = 'https://paste.ee/api/v1/pastes' - headers = { - 'X-Auth-Token': api_key - } - data = { - 'description': 'File content', - 'sections': [{'contents': content}] - } - response = requests.post(url, json=data, headers=headers) - if response.status_code == 201: - return response.json()['link'] - return None -def publish_to_multiple_pastebins(file_path, pasteee_api_key=None): +def publish_to_pastesh(content): + HOST = 'https://paste.sh' + VERSION = 'v2' + + # Generate ID + id = f"p{secrets.token_urlsafe(6)}" + + # Create a temporary file + with tempfile.NamedTemporaryFile(mode='w+', delete=False) as temp_file: + temp_file_path = temp_file.name + + # Write content to the temporary file + temp_file.write(content) + temp_file.flush() + + try: + # Prepare headers + headers = { + 'X-Server-Key': '', + 'Content-type': f'text/vnd.paste.sh-{VERSION}' + } + + # Send the request + with open(temp_file_path, 'rb') as file: + response = requests.post(f"{HOST}/{id}", headers=headers, data=file) + + if response.status_code == 200: + return f"{HOST}/{id}" + else: + print(f"Error: Status code {response.status_code}") + print(f"Response: {response.text}") + return None + + finally: + # Clean up the temporary file + os.unlink(temp_file_path) + + +def publish_to_multiple_pastebins(file_path): # Read the file content with open(file_path, 'r') as file: file_content = file.read() @@ -81,29 +102,22 @@ def publish_to_multiple_pastebins(file_path, pasteee_api_key=None): if mozilla_url: results['Mozilla Pastebin'] = mozilla_url - # Publish to Paste2 - paste2_url = publish_to_paste2(file_content) - if paste2_url: - results['Paste2'] = paste2_url - # Publish to Dpaste dpaste_url = publish_to_dpaste(file_content) if dpaste_url: results['Dpaste'] = dpaste_url - # Publish to Paste.ee (if API key is provided) - if pasteee_api_key: - pasteee_url = publish_to_pasteee(file_content, pasteee_api_key) - if pasteee_url: - results['Paste.ee'] = pasteee_url + # Publish to CPaste + cpaste_url = publish_to_cpaste(file_path) + if cpaste_url: + results['CPaste'] = cpaste_url + return results # Example usage file_path = 'test.txt' -#pasteee_api_key = 'YOUR_PASTE_EE_API_KEY' # Optional, remove if not using - results = publish_to_multiple_pastebins(file_path) for service, url in results.items(): diff --git a/test.txt b/test.txt deleted file mode 100644 index c49a42b..0000000 --- a/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is a test file. Please be gentle with me. Gentlemens.