Now we have working paste sending to 4 places?. PrivateBin client is functional, we can use any PrivateBin with it!
This commit is contained in:
parent
9977b834e2
commit
fa369d636f
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
__pycache__/
|
||||
*.txt
|
423
cpaste.py
Normal file
423
cpaste.py
Normal file
@ -0,0 +1,423 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
#######################################################################
|
||||
#
|
||||
# A script to paste to https://cpaste.org/
|
||||
#
|
||||
# Copyright (c) 2013-2019 Andreas Schneider <asn@samba.org>
|
||||
# Copyright (c) 2013 Alexander Bokovoy <ab@samba.org>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
#######################################################################
|
||||
#
|
||||
# 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()
|
84
mpaste.py
84
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'
|
||||
|
||||
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-Auth-Token': api_key
|
||||
'X-Server-Key': '',
|
||||
'Content-type': f'text/vnd.paste.sh-{VERSION}'
|
||||
}
|
||||
data = {
|
||||
'description': 'File content',
|
||||
'sections': [{'contents': content}]
|
||||
}
|
||||
response = requests.post(url, json=data, headers=headers)
|
||||
if response.status_code == 201:
|
||||
return response.json()['link']
|
||||
|
||||
# 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
|
||||
|
||||
def publish_to_multiple_pastebins(file_path, pasteee_api_key=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():
|
||||
|
Loading…
Reference in New Issue
Block a user