forked from r4sas/PBinCLI
Compare commits
56 Commits
Author | SHA1 | Date | |
---|---|---|---|
ab7b2c060c | |||
b35fd9f4b9 | |||
d8f3c44371 | |||
7117b2bcd7 | |||
c860fec339 | |||
7ecb501627 | |||
7b90b6a772 | |||
a9a4079855 | |||
3965efef89 | |||
51170975c7 | |||
cc791da03c | |||
906c14fddf | |||
0e61d05c9f | |||
05c1938aa6 | |||
369738ea50 | |||
![]() |
7bd3ef22b9 | ||
![]() |
f05f65ea61 | ||
3c2cdb10ac | |||
28f7c09f83 | |||
b4ffc1a06b | |||
58645e1d32 | |||
82ca95f01a | |||
5589ba0437 | |||
6c9a5c95b6 | |||
![]() |
505a0104ca | ||
94023a986d | |||
5909e7330b | |||
d0f23094bc | |||
86061e595c | |||
49362c70a6 | |||
70328903fa | |||
29498b9315 | |||
19f130feb1 | |||
682b47fbd3 | |||
9d82c727b6 | |||
c425d86ed6 | |||
3ed06686ab | |||
7e4fb0a8c4 | |||
ebfe0c48a0 | |||
45d854e590 | |||
18d79c8e04 | |||
92c38344e3 | |||
b596f42b7e | |||
86c5051fcf | |||
f838f8ee94 | |||
fb7a93732d | |||
181763070c | |||
c3a491ac46 | |||
641c55a6a2 | |||
432675f2e6 | |||
635c87dabd | |||
6b6c33e545 | |||
7c5ba2fdbe | |||
692335ee62 | |||
![]() |
c63256c628 | ||
![]() |
66659da66d |
4
.gitattributes
vendored
Normal file
4
.gitattributes
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.github/** export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
README.md export-ignore
|
8
.github/dependabot.yml
vendored
Normal file
8
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: pip
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "17:00"
|
||||
open-pull-requests-limit: 10
|
24
.github/workflows/binary.yml
vendored
Normal file
24
.github/workflows/binary.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
name: Package Application with Pyinstaller
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Binary for windows
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup python 3.8
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Build executable with pyinstaller
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install .
|
||||
pip install pyinstaller pywin32-ctypes
|
||||
pyinstaller pbincli.spec
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: pbincli-windows
|
||||
path: dist/*
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -54,6 +54,7 @@ MANIFEST
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
-pbincli.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
@ -131,3 +132,6 @@ venv.bak/
|
||||
|
||||
# PyCharm project settings
|
||||
.idea
|
||||
|
||||
# PBinCLI downloaded paste text
|
||||
paste-*.txt
|
||||
|
3
MANIFEST.in
Normal file
3
MANIFEST.in
Normal file
@ -0,0 +1,3 @@
|
||||
include LICENSE
|
||||
include README.rst
|
||||
include requirements.txt
|
142
README.md
142
README.md
@ -1,73 +1,139 @@
|
||||
[](https://github.com/r4sas/PBinCLI/blob/master/LICENSE)
|
||||
[](https://github.com/r4sas/PBinCLI/tags/)
|
||||
[](https://www.codacy.com/app/r4sas/PBinCLI?utm_source=github.com&utm_medium=referral&utm_content=r4sas/PBinCLI&utm_campaign=Badge_Grade)
|
||||
[](https://www.codacy.com/gh/r4sas/PBinCLI/dashboard?utm_source=github.com&utm_medium=referral&utm_content=r4sas/PBinCLI&utm_campaign=Badge_Grade)
|
||||
|
||||
PBinCLI
|
||||
=====
|
||||
# PBinCLI
|
||||
|
||||
PBinCLI is command line client for [PrivateBin](https://github.com/PrivateBin/PrivateBin/) written on Python 3.
|
||||
PBinCLI is a command line client for [PrivateBin](https://github.com/PrivateBin/PrivateBin/) written in Python 3.
|
||||
|
||||
Installing
|
||||
-----
|
||||
# Installation
|
||||
|
||||
Installing globally using pip3:
|
||||
```bash
|
||||
virtualenv --python=python3 venv
|
||||
pip3 install pbincli
|
||||
```
|
||||
|
||||
Installing with `virtualenv`:
|
||||
```bash
|
||||
python3 -m virtualenv --python=python3 venv
|
||||
. venv/bin/activate
|
||||
pip install pbincli
|
||||
```
|
||||
|
||||
Usage
|
||||
-----
|
||||
By default pbincli configured to use `https://paste.i2pd.xyz/` for sending and receiving pastes. No proxy used by default.
|
||||
*Note*: if you used `virtualenv` installation method, don't forget to activate your virtual environment before running the tool: call `. /path/to/venv/bin/activate` in terminal
|
||||
|
||||
You can create config file with variables `server` and `proxy` in `~/.config/pbincli/pbincli.conf` to use different settings.
|
||||
# Configuration
|
||||
|
||||
Example contents:
|
||||
By default PBinCLI is configured to use `https://paste.i2pd.xyz/` for sending and receiving pastes. No proxy is used by default.
|
||||
|
||||
You can always create a config file to use different settings.
|
||||
|
||||
Configuration file is expected to be found in `~/.config/pbincli/pbincli.conf`, `%APPDATA%/pbincli/pbincli.conf` (Windows) and `~/Library/Application Support/pbincli/pbincli.conf` (MacOS)
|
||||
|
||||
## Example of config file content
|
||||
|
||||
```ini
|
||||
server=https://paste.i2pd.xyz/
|
||||
proxy=http://127.0.0.1:3128
|
||||
```
|
||||
|
||||
Run inside `venv` command:
|
||||
## List of OPTIONS available
|
||||
|
||||
| Option | Default | Possible value |
|
||||
|----------------------|-------------------------|----------------|
|
||||
| server | https://paste.i2pd.xyz/ | Domain ending with slash |
|
||||
| mirrors | None | Domains separated with comma, like `http://privatebin.ygg/,http://privatebin.i2p/` |
|
||||
| proxy | None | Proxy address starting with scheme `http://` or `socks5://` |
|
||||
| expire | 1day | 5min / 10min / 1hour / 1day / 1week / 1month / 1year / never |
|
||||
| burn | False | True / False |
|
||||
| discus | False | True / False |
|
||||
| format | plaintext | plaintext / syntaxhighlighting / markdown |
|
||||
| short | False | True / False |
|
||||
| short_api | None | `tinyurl`, `clckru`, `isgd`, `vgd`, `cuttly`, `yourls`, `custom` |
|
||||
| short_url | None | Domain name of shortener service for `yourls`, or URL (with required parameters) for `custom` |
|
||||
| short_user | None | Used only in `yourls` |
|
||||
| short_pass | None | Used only in `yourls` |
|
||||
| short_token | None | Used only in `yourls` |
|
||||
| no_check_certificate | False | True / False |
|
||||
| no_insecure_warning | False | True / False |
|
||||
| compression | zlib | zlib / none |
|
||||
|
||||
# Usage
|
||||
|
||||
PBinCLI tool is started with `pbincli` command. Detailed help on command usage is provided with `-h` option:
|
||||
```bash
|
||||
pbincli send --text "Hello!"
|
||||
pbincli {send|get|delete} -h
|
||||
```
|
||||
|
||||
Or use stdin input to read text for paste:
|
||||
## Sending
|
||||
|
||||
* Sending text:
|
||||
```bash
|
||||
pbincli send -t "Hello! This is a test paste!"
|
||||
```
|
||||
|
||||
* Using stdin input to read text into a paste:
|
||||
```bash
|
||||
pbincli send - <<EOF
|
||||
Hello! This is test paste!
|
||||
Hello! This is a test paste!
|
||||
EOF
|
||||
```
|
||||
|
||||
It will send string `Hello! This is test paste!` to PrivateBin.
|
||||
|
||||
To send file use `--file` or `-f` with filename. Example:
|
||||
|
||||
* Sending a file with text attached into a paste:
|
||||
```bash
|
||||
pbincli send -c "My document" -f info.pdf
|
||||
pbincli send -f info.pdf -t "I'm sending my document."
|
||||
```
|
||||
|
||||
To retrieve paste from server, use `get` command with paste info.
|
||||
|
||||
It must be formated like `pasteID#passphrase`. Example:
|
||||
|
||||
* Sending a file only with no text attached:
|
||||
```bash
|
||||
pbincli get 49eeb1326cfa9491#vfeortoVWaYeJlviDdhxQBtj5e0I2kArpynrtu/tnGs=
|
||||
```
|
||||
More info you can find by typing
|
||||
|
||||
```bash
|
||||
pbincli [-h] {send, get, delete}
|
||||
pbincli send -q -f info.pdf
|
||||
```
|
||||
|
||||
TODO
|
||||
----
|
||||
Write a more complete usage documentation.
|
||||
### Other options
|
||||
|
||||
License
|
||||
-------
|
||||
This project is licensed under the MIT license, which can be found in the file
|
||||
[LICENSE](https://github.com/r4sas/PBinCLI/blob/master/LICENSE) in the root of the project source code.
|
||||
It is also possible to set-up paste parameters such as "burn after reading", expiritaion time, formatting, enabling discussions and changing compression algorithm. Please refer to `pbincli send -h` output for more information.
|
||||
|
||||
## Receiving
|
||||
|
||||
To retrieve a paste from a server, you need to use `get` command with the paste info.
|
||||
|
||||
Paste info must be formated as `pasteID#Passphrase` or just use full URL to a paste. Example:
|
||||
```bash
|
||||
pbincli get "xxx#yyy" ### receive paste xxx from https://paste.i2pd.xyz/ by default
|
||||
pbincli get "https://example.com/?xxx#yyy" ### receive paste xxx from https://example.com/
|
||||
```
|
||||
|
||||
## Deletion
|
||||
|
||||
To delete a paste from a server, use `delete` command with paste info:
|
||||
```bash
|
||||
pbincli delete "pasteid=xxx&deletetoken=yyy" ### delete paste xxx from https://paste.i2pd.xyz/ by default
|
||||
pbincli delete "https://example.com/?pasteid=xxx&deletetoken=yyy" ### delete paste xxx from https://example.com/
|
||||
```
|
||||
|
||||
If you need to delete a paste on different server than the configured one, use `-s` option together with the instance URL.
|
||||
|
||||
# Additional examples
|
||||
|
||||
Here you can find additional examples.
|
||||
|
||||
## Usage with I2P enabled services
|
||||
|
||||
Change settings to set server to `http://privatebin.i2p/` and proxy to `http://127.0.0.1:4444`. Configuration file for this example is:
|
||||
```ini
|
||||
server=http://privatebin.i2p/
|
||||
proxy=http://127.0.0.1:4444
|
||||
```
|
||||
|
||||
## Using aliases
|
||||
|
||||
Example of alias to send a paste from `stdin` direclty to I2P service:
|
||||
```bash
|
||||
alias pastei2p="echo 'paste the text to stdin' && pbincli send -s http://privatebin.i2p/ -x http://127.0.0.1:4444 -"
|
||||
```
|
||||
|
||||
Call it by running `pastei2p` in terminal.
|
||||
|
||||
# License
|
||||
|
||||
This project is licensed under the MIT license, which can be found in the file [LICENSE](https://github.com/r4sas/PBinCLI/blob/master/LICENSE) in the root of the project source code.
|
||||
|
310
README.rst
310
README.rst
@ -1,91 +1,219 @@
|
||||
|
||||
|
||||
.. image:: https://img.shields.io/github/license/r4sas/PBinCLI.svg
|
||||
:target: https://github.com/r4sas/PBinCLI/blob/master/LICENSE
|
||||
:alt: GitHub license
|
||||
|
||||
|
||||
.. image:: https://img.shields.io/github/tag/r4sas/PBinCLI.svg
|
||||
:target: https://github.com/r4sas/PBinCLI/tags/
|
||||
:alt: GitHub tag
|
||||
|
||||
|
||||
.. image:: https://api.codacy.com/project/badge/Grade/4f24f43356a84621bbd9078c4b3f1b70
|
||||
:target: https://www.codacy.com/app/r4sas/PBinCLI?utm_source=github.com&utm_medium=referral&utm_content=r4sas/PBinCLI&utm_campaign=Badge_Grade
|
||||
:alt: Codacy Badge
|
||||
|
||||
|
||||
PBinCLI
|
||||
=======
|
||||
|
||||
PBinCLI is command line client for `PrivateBin <https://github.com/PrivateBin/PrivateBin/>`_ written on Python 3.
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
virtualenv --python=python3 venv
|
||||
. venv/bin/activate
|
||||
pip install pbincli
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
By default pbincli configured to use ``https://paste.i2pd.xyz/`` for sending and receiving pastes. No proxy used by default.
|
||||
|
||||
You can create config file with variables ``server`` and ``proxy`` in ``~/.config/pbincli/pbincli.conf`` to use different settings.
|
||||
|
||||
Example contents:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
server=https://paste.i2pd.xyz/
|
||||
proxy=http://127.0.0.1:3128
|
||||
|
||||
Run inside ``venv`` command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pbincli send --text "Hello!"
|
||||
|
||||
Or use stdin input to read text for paste:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pbincli send - <<EOF
|
||||
Hello! This is test paste!
|
||||
EOF
|
||||
|
||||
It will send string ``Hello! This is test paste!`` to PrivateBin.
|
||||
|
||||
To send file use ``--file`` or ``-f`` with filename. Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pbincli send -c "My document" -f info.pdf
|
||||
|
||||
To retrieve paste from server, use ``get`` command with paste info.
|
||||
|
||||
It must be formated like ``pasteID#passphrase``. Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pbincli get 49eeb1326cfa9491#vfeortoVWaYeJlviDdhxQBtj5e0I2kArpynrtu/tnGs=
|
||||
|
||||
More info you can find by typing
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pbincli [-h] {send, get, delete}
|
||||
|
||||
TODO
|
||||
----
|
||||
|
||||
Write a more complete usage documentation.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
This project is licensed under the MIT license, which can be found in the file
|
||||
`LICENSE <https://github.com/r4sas/PBinCLI/blob/master/LICENSE>`_ in the root of the project source code.
|
||||
|
||||
|
||||
.. image:: https://img.shields.io/github/license/r4sas/PBinCLI.svg
|
||||
:target: https://github.com/r4sas/PBinCLI/blob/master/LICENSE
|
||||
:alt: GitHub license
|
||||
|
||||
|
||||
.. image:: https://img.shields.io/github/tag/r4sas/PBinCLI.svg
|
||||
:target: https://github.com/r4sas/PBinCLI/tags/
|
||||
:alt: GitHub tag
|
||||
|
||||
|
||||
.. image:: https://app.codacy.com/project/badge/Grade/4f24f43356a84621bbd9078c4b3f1b70
|
||||
:target: https://www.codacy.com/gh/r4sas/PBinCLI/dashboard?utm_source=github.com&utm_medium=referral&utm_content=r4sas/PBinCLI&utm_campaign=Badge_Grade
|
||||
:alt: Codacy Badge
|
||||
|
||||
|
||||
PBinCLI
|
||||
=======
|
||||
|
||||
PBinCLI is a command line client for `PrivateBin <https://github.com/PrivateBin/PrivateBin/>`_ written in Python 3.
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
Installing globally using pip3:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip3 install pbincli
|
||||
|
||||
Installing with ``virtualenv``\ :
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python3 -m virtualenv --python=python3 venv
|
||||
. venv/bin/activate
|
||||
pip install pbincli
|
||||
|
||||
*Note*\ : if you used ``virtualenv`` installation method, don't forget to activate your virtual environment before running the tool: call ``. /path/to/venv/bin/activate`` in terminal
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
By default PBinCLI is configured to use ``https://paste.i2pd.xyz/`` for sending and receiving pastes. No proxy is used by default.
|
||||
|
||||
You can always create a config file to use different settings.
|
||||
|
||||
Configuration file is expected to be found in ``~/.config/pbincli/pbincli.conf``\ , ``%APPDATA%/pbincli/pbincli.conf`` (Windows) and ``~/Library/Application Support/pbincli/pbincli.conf`` (MacOS)
|
||||
|
||||
Example of config file content
|
||||
------------------------------
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
server=https://paste.i2pd.xyz/
|
||||
proxy=http://127.0.0.1:3128
|
||||
|
||||
List of OPTIONS available
|
||||
-------------------------
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Option
|
||||
- Default
|
||||
- Possible value
|
||||
* - server
|
||||
- https://paste.i2pd.xyz/
|
||||
- Domain ending with slash
|
||||
* - mirrors
|
||||
- None
|
||||
- Domains separated with comma, like ``http://privatebin.ygg/,http://privatebin.i2p/``
|
||||
* - proxy
|
||||
- None
|
||||
- Proxy address starting with scheme ``http://`` or ``socks5://``
|
||||
* - expire
|
||||
- 1day
|
||||
- 5min / 10min / 1hour / 1day / 1week / 1month / 1year / never
|
||||
* - burn
|
||||
- False
|
||||
- True / False
|
||||
* - discus
|
||||
- False
|
||||
- True / False
|
||||
* - format
|
||||
- plaintext
|
||||
- plaintext / syntaxhighlighting / markdown
|
||||
* - short
|
||||
- False
|
||||
- True / False
|
||||
* - short_api
|
||||
- None
|
||||
- ``tinyurl``\ , ``clckru``\ , ``isgd``\ , ``vgd``\ , ``cuttly``\ , ``yourls``\ , ``custom``
|
||||
* - short_url
|
||||
- None
|
||||
- Domain name of shortener service for ``yourls``\ , or URL (with required parameters) for ``custom``
|
||||
* - short_user
|
||||
- None
|
||||
- Used only in ``yourls``
|
||||
* - short_pass
|
||||
- None
|
||||
- Used only in ``yourls``
|
||||
* - short_token
|
||||
- None
|
||||
- Used only in ``yourls``
|
||||
* - no_check_certificate
|
||||
- False
|
||||
- True / False
|
||||
* - no_insecure_warning
|
||||
- False
|
||||
- True / False
|
||||
* - compression
|
||||
- zlib
|
||||
- zlib / none
|
||||
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
PBinCLI tool is started with ``pbincli`` command. Detailed help on command usage is provided with ``-h`` option:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pbincli {send|get|delete} -h
|
||||
|
||||
Sending
|
||||
-------
|
||||
|
||||
|
||||
*
|
||||
Sending text:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pbincli send -t "Hello! This is a test paste!"
|
||||
|
||||
*
|
||||
Using stdin input to read text into a paste:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pbincli send - <<EOF
|
||||
Hello! This is a test paste!
|
||||
EOF
|
||||
|
||||
*
|
||||
Sending a file with text attached into a paste:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pbincli send -f info.pdf -t "I'm sending my document."
|
||||
|
||||
*
|
||||
Sending a file only with no text attached:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pbincli send -q -f info.pdf
|
||||
|
||||
Other options
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
It is also possible to set-up paste parameters such as "burn after reading", expiritaion time, formatting, enabling discussions and changing compression algorithm. Please refer to ``pbincli send -h`` output for more information.
|
||||
|
||||
Receiving
|
||||
---------
|
||||
|
||||
To retrieve a paste from a server, you need to use ``get`` command with the paste info.
|
||||
|
||||
Paste info must be formated as ``pasteID#Passphrase`` or just use full URL to a paste. Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pbincli get "xxx#yyy" ### receive paste xxx from https://paste.i2pd.xyz/ by default
|
||||
pbincli get "https://example.com/?xxx#yyy" ### receive paste xxx from https://example.com/
|
||||
|
||||
Deletion
|
||||
--------
|
||||
|
||||
To delete a paste from a server, use ``delete`` command with paste info:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pbincli delete "pasteid=xxx&deletetoken=yyy" ### delete paste xxx from https://paste.i2pd.xyz/ by default
|
||||
pbincli delete "https://example.com/?pasteid=xxx&deletetoken=yyy" ### delete paste xxx from https://example.com/
|
||||
|
||||
If you need to delete a paste on different server than the configured one, use ``-s`` option together with the instance URL.
|
||||
|
||||
Additional examples
|
||||
===================
|
||||
|
||||
Here you can find additional examples.
|
||||
|
||||
Usage with I2P enabled services
|
||||
-------------------------------
|
||||
|
||||
Change settings to set server to ``http://privatebin.i2p/`` and proxy to ``http://127.0.0.1:4444``. Configuration file for this example is:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
server=http://privatebin.i2p/
|
||||
proxy=http://127.0.0.1:4444
|
||||
|
||||
Using aliases
|
||||
-------------
|
||||
|
||||
Example of alias to send a paste from ``stdin`` direclty to I2P service:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
alias pastei2p="echo 'paste the text to stdin' && pbincli send -s http://privatebin.i2p/ -x http://127.0.0.1:4444 -"
|
||||
|
||||
Call it by running ``pastei2p`` in terminal.
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
This project is licensed under the MIT license, which can be found in the file `LICENSE <https://github.com/r4sas/PBinCLI/blob/master/LICENSE>`_ in the root of the project source code.
|
||||
|
BIN
contrib/privatebin.ico
Normal file
BIN
contrib/privatebin.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
67
pbincli.spec
Normal file
67
pbincli.spec
Normal file
@ -0,0 +1,67 @@
|
||||
# -*- mode: python -*-
|
||||
|
||||
from pkg_resources import parse_version
|
||||
from PyInstaller.utils.win32.versioninfo import VSVersionInfo, FixedFileInfo, StringFileInfo, StringTable, StringStruct, VarFileInfo, VarStruct
|
||||
from pbincli.__init__ import __version__ as pbincli_version, __copyright__ as pbincli_copyright
|
||||
|
||||
pbincli_ver = parse_version(pbincli_version)
|
||||
|
||||
block_cipher = None
|
||||
|
||||
a = Analysis(['pbincli\\cli.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False)
|
||||
pyz = PYZ(a.pure, a.zipped_data,
|
||||
cipher=block_cipher)
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='pbincli-' + pbincli_version,
|
||||
version=VSVersionInfo(
|
||||
ffi=FixedFileInfo(
|
||||
filevers=(pbincli_ver.major, pbincli_ver.minor, pbincli_ver.micro, 0),
|
||||
prodvers=(pbincli_ver.major, pbincli_ver.minor, pbincli_ver.micro, 0),
|
||||
mask=0x3f,
|
||||
flags=0x0,
|
||||
OS=0x40004,
|
||||
fileType=0x1,
|
||||
subtype=0x0,
|
||||
date=(0, 0)
|
||||
),
|
||||
kids=[
|
||||
StringFileInfo([
|
||||
StringTable(
|
||||
u'040904B0',
|
||||
[
|
||||
StringStruct(u'FileDescription', u'PrivateBin CLI'),
|
||||
StringStruct(u'FileVersion', pbincli_version),
|
||||
StringStruct(u'InternalName', u'pbincli'),
|
||||
StringStruct(u'LegalCopyright', pbincli_copyright),
|
||||
StringStruct(u'OriginalFilename', u'pbincli-' + pbincli_version + u'.exe'),
|
||||
StringStruct(u'ProductName', u'PBinCLI'),
|
||||
StringStruct(u'ProductVersion', pbincli_version)
|
||||
]
|
||||
)
|
||||
]),
|
||||
VarFileInfo([VarStruct(u'Translation', [1033, 1200])])
|
||||
]
|
||||
),
|
||||
icon=['contrib\\privatebin.ico'],
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
runtime_tmpdir=None,
|
||||
console=True)
|
@ -2,6 +2,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__author__ = "R4SAS <r4sas@i2pmail.org>"
|
||||
__version__ = "0.2"
|
||||
__version__ = "0.3.4"
|
||||
__copyright__ = "Copyright (c) R4SAS"
|
||||
__license__ = "MIT"
|
||||
|
@ -1,26 +1,47 @@
|
||||
from pbincli.format import Paste
|
||||
import signal, sys
|
||||
from urllib.parse import parse_qsl
|
||||
|
||||
from pbincli.api import Shortener
|
||||
from pbincli.format import Paste
|
||||
from pbincli.utils import PBinCLIError, check_writable, json_encode, uri_validator, validate_url_ending
|
||||
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
print('Keyboard interrupt received, terminating…')
|
||||
sys.exit(0)
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
|
||||
def send(args, api_client, settings=None):
|
||||
if settings['short']:
|
||||
shortener = Shortener(settings)
|
||||
|
||||
def send(args, api_client):
|
||||
if not args.notext:
|
||||
if args.text:
|
||||
text = args.text
|
||||
elif args.stdin:
|
||||
print("Reading text from stdin…")
|
||||
text = args.stdin.read()
|
||||
elif not args.file:
|
||||
print("Nothing to send!")
|
||||
exit(1)
|
||||
PBinCLIError("Nothing to send!")
|
||||
else:
|
||||
text = ""
|
||||
|
||||
print("Preparing paste…")
|
||||
paste = Paste(args.debug)
|
||||
|
||||
if args.verbose: print("Used server: {}".format(api_client.getServer()))
|
||||
|
||||
# get from server supported paste format version and update object
|
||||
if args.verbose: print("Getting supported paste format version from server…")
|
||||
version = api_client.getVersion()
|
||||
paste.setVersion(version)
|
||||
|
||||
if args.verbose: print("Filling paste with data…")
|
||||
# set compression type, works only on v2 pastes
|
||||
if version == 2:
|
||||
paste.setCompression(args.compression)
|
||||
paste.setCompression(settings['compression'])
|
||||
|
||||
# add text in paste (if it provided)
|
||||
paste.setText(text)
|
||||
@ -33,21 +54,22 @@ def send(args, api_client):
|
||||
if args.file:
|
||||
paste.setAttachment(args.file)
|
||||
|
||||
if args.verbose: print("Encrypting paste…")
|
||||
paste.encrypt(
|
||||
formatter = args.format,
|
||||
burnafterreading = args.burn,
|
||||
discussion = args.discus,
|
||||
expiration = args.expire)
|
||||
formatter = settings['format'],
|
||||
burnafterreading = settings['burn'],
|
||||
discussion = settings['discus'],
|
||||
expiration = settings['expire'])
|
||||
|
||||
if args.verbose: print("Sending request to server…")
|
||||
request = paste.getJSON()
|
||||
|
||||
if args.debug:
|
||||
print("Passphrase:\t{}".format(paste.getHash()))
|
||||
print("Request:\t{}".format(request))
|
||||
if args.debug: print("Passphrase:\t{}\nRequest:\t{}".format(paste.getHash(), request))
|
||||
|
||||
# If we use dry option, exit now
|
||||
if args.dry: exit(0)
|
||||
if args.dry: sys.exit(0)
|
||||
|
||||
print("Uploading paste…")
|
||||
result = api_client.post(request)
|
||||
|
||||
if args.debug: print("Response:\t{}\n".format(result))
|
||||
@ -56,34 +78,61 @@ def send(args, api_client):
|
||||
if not result['status']: # return code is zero
|
||||
passphrase = paste.getHash()
|
||||
|
||||
print("Paste uploaded!\nPasteID:\t{}\nPassword:\t{}\nDelete token:\t{}\n\nLink:\t\t{}?{}#{}".format(
|
||||
# Paste information
|
||||
print("Paste uploaded!\nPasteID:\t{}\nPassword:\t{}\nDelete token:\t{}".format(
|
||||
result['id'],
|
||||
passphrase,
|
||||
result['deletetoken'],
|
||||
api_client.server,
|
||||
result['deletetoken']))
|
||||
|
||||
# Paste link
|
||||
print("\nLink:\t\t{}?{}#{}".format(
|
||||
settings['server'],
|
||||
result['id'],
|
||||
passphrase))
|
||||
|
||||
# Paste deletion link
|
||||
print("Delete Link:\t{}?pasteid={}&deletetoken={}".format(
|
||||
settings['server'],
|
||||
result['id'],
|
||||
result['deletetoken']))
|
||||
|
||||
# Print links to mirrors if present
|
||||
if settings['mirrors']:
|
||||
print("\nMirrors:")
|
||||
urls = settings['mirrors'].split(',')
|
||||
for x in urls:
|
||||
print("\t\t{}?{}#{}".format(
|
||||
validate_url_ending(x),
|
||||
result['id'],
|
||||
passphrase))
|
||||
|
||||
elif result['status']: # return code is other then zero
|
||||
print("Something went wrong...\nError:\t\t{}".format(result['message']))
|
||||
exit(1)
|
||||
PBinCLIError("Something went wrong…\nError:\t\t{}".format(result['message']))
|
||||
else: # or here no status field in response or it is empty
|
||||
print("Something went wrong...\nError: Empty response.")
|
||||
exit(1)
|
||||
PBinCLIError("Something went wrong…\nError: Empty response.")
|
||||
|
||||
if settings['short']:
|
||||
print("\nQuerying URL shortening service…")
|
||||
shortener.getlink("{}?{}#{}".format(
|
||||
settings['server'],
|
||||
result['id'],
|
||||
passphrase))
|
||||
|
||||
|
||||
def get(args, api_client):
|
||||
from pbincli.utils import check_writable, json_encode
|
||||
def get(args, api_client, settings=None):
|
||||
parseduri, isuri = uri_validator(args.pasteinfo)
|
||||
|
||||
try:
|
||||
pasteid, passphrase = args.pasteinfo.split("#")
|
||||
except ValueError:
|
||||
print("PBinCLI error: provided info hasn't contain valid PasteID#Passphrase string")
|
||||
exit(1)
|
||||
|
||||
if not (pasteid and passphrase):
|
||||
print("PBinCLI error: Incorrect request")
|
||||
exit(1)
|
||||
if isuri and parseduri.query and parseduri.fragment:
|
||||
api_client.server = args.pasteinfo.split("?")[0]
|
||||
pasteid = parseduri.query
|
||||
passphrase = parseduri.fragment
|
||||
elif parseduri.path and parseduri.path != "/" and parseduri.fragment:
|
||||
pasteid = parseduri.path
|
||||
passphrase = parseduri.fragment
|
||||
else:
|
||||
PBinCLIError("Provided info hasn't contain valid URL or PasteID#Passphrase string")
|
||||
|
||||
if args.verbose: print("Used server: {}".format(api_client.getServer()))
|
||||
if args.debug: print("PasteID:\t{}\nPassphrase:\t{}".format(pasteid, passphrase))
|
||||
|
||||
paste = Paste(args.debug)
|
||||
@ -92,13 +141,14 @@ def get(args, api_client):
|
||||
paste.setPassword(args.password)
|
||||
if args.debug: print("Password:\t{}".format(args.password))
|
||||
|
||||
if args.verbose: print("Requesting paste from server…")
|
||||
result = api_client.get(pasteid)
|
||||
|
||||
if args.debug: print("Response:\t{}\n".format(result))
|
||||
|
||||
# Paste was received. Checking received status code
|
||||
if not result['status']: # return code is zero
|
||||
print("Paste received!")
|
||||
print("Paste received! Decoding…")
|
||||
|
||||
version = result['v'] if 'v' in result else 1
|
||||
paste.setVersion(version)
|
||||
@ -135,23 +185,32 @@ def get(args, api_client):
|
||||
f.close()
|
||||
|
||||
if version == 1 and 'meta' in result and 'burnafterreading' in result['meta'] and result['meta']['burnafterreading']:
|
||||
print("Burn afrer reading flag found. Deleting paste...")
|
||||
print("Burn afrer reading flag found. Deleting paste…")
|
||||
api_client.delete(json_encode({'pasteid':pasteid,'deletetoken':'burnafterreading'}))
|
||||
|
||||
elif result['status']: # return code is other then zero
|
||||
print("Something went wrong...\nError:\t\t{}".format(result['message']))
|
||||
exit(1)
|
||||
PBinCLIError("Something went wrong…\nError:\t\t{}".format(result['message']))
|
||||
else: # or here no status field in response or it is empty
|
||||
print("Something went wrong...\nError: Empty response.")
|
||||
exit(1)
|
||||
PBinCLIError("Something went wrong…\nError: Empty response.")
|
||||
|
||||
|
||||
def delete(args, api_client):
|
||||
from pbincli.utils import json_encode
|
||||
def delete(args, api_client, settings=None):
|
||||
parseduri, isuri = uri_validator(args.pasteinfo)
|
||||
|
||||
pasteid = args.paste
|
||||
token = args.token
|
||||
if isuri:
|
||||
api_client.server = args.pasteinfo.split("?")[0]
|
||||
query = dict(parse_qsl(parseduri.query))
|
||||
else:
|
||||
query = dict(parse_qsl(args.pasteinfo))
|
||||
|
||||
if 'pasteid' in query and 'deletetoken' in query:
|
||||
pasteid = query['pasteid']
|
||||
token = query['deletetoken']
|
||||
else:
|
||||
PBinCLIError("Provided info hasn't contain required information")
|
||||
|
||||
if args.verbose: print("Used server: {}".format(api_client.getServer()))
|
||||
if args.debug: print("PasteID:\t{}\nToken:\t\t{}".format(pasteid, token))
|
||||
|
||||
print("Requesting paste deletion…")
|
||||
api_client.delete(json_encode({'pasteid':pasteid,'deletetoken':token}))
|
||||
|
239
pbincli/api.py
239
pbincli/api.py
@ -1,21 +1,29 @@
|
||||
import requests
|
||||
from requests import HTTPError
|
||||
from pbincli.utils import PBinCLIError
|
||||
|
||||
def _config_requests(settings=None):
|
||||
if settings['proxy']:
|
||||
proxy = {settings['proxy'].split('://')[0]: settings['proxy']}
|
||||
else:
|
||||
proxy = {}
|
||||
|
||||
if settings['no_insecure_warning']:
|
||||
from requests.packages.urllib3.exceptions import InsecureRequestWarning
|
||||
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
||||
|
||||
session = requests.Session()
|
||||
session.verify = not settings['no_check_certificate']
|
||||
|
||||
return session, proxy
|
||||
|
||||
|
||||
class PrivateBin:
|
||||
def __init__(self, server, settings=None):
|
||||
self.server = server
|
||||
def __init__(self, settings=None):
|
||||
self.server = settings['server']
|
||||
self.headers = {'X-Requested-With': 'JSONHttpRequest'}
|
||||
|
||||
if settings['proxy']:
|
||||
self.proxy = {settings['proxy'].split('://')[0]: settings['proxy']}
|
||||
else:
|
||||
self.proxy = {}
|
||||
|
||||
if settings['noinsecurewarn']:
|
||||
from requests.packages.urllib3.exceptions import InsecureRequestWarning
|
||||
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
||||
|
||||
self.session = requests.Session()
|
||||
self.session.verify = settings['nocheckcert']
|
||||
self.session, self.proxy = _config_requests(settings)
|
||||
|
||||
def post(self, request):
|
||||
result = self.session.post(
|
||||
@ -27,8 +35,7 @@ class PrivateBin:
|
||||
try:
|
||||
return result.json()
|
||||
except ValueError:
|
||||
print("ERROR: Unable parse response as json. Received (size = {}):\n{}".format(len(result.text), result.text))
|
||||
exit(1)
|
||||
PBinCLIError("Unable parse response as json. Received (size = {}):\n{}".format(len(result.text), result.text))
|
||||
|
||||
|
||||
def get(self, request):
|
||||
@ -56,11 +63,9 @@ class PrivateBin:
|
||||
if not result['status']:
|
||||
print("Paste successfully deleted!")
|
||||
elif result['status']:
|
||||
print("Something went wrong...\nError:\t\t{}".format(result['message']))
|
||||
exit(1)
|
||||
PBinCLIError("Something went wrong...\nError:\t\t{}".format(result['message']))
|
||||
else:
|
||||
print("Something went wrong...\nError: Empty response.")
|
||||
exit(1)
|
||||
PBinCLIError("Something went wrong...\nError: Empty response.")
|
||||
|
||||
|
||||
def getVersion(self):
|
||||
@ -72,3 +77,199 @@ class PrivateBin:
|
||||
'v' in jsonldSchema['@context'] and
|
||||
'@value' in jsonldSchema['@context']['v']) \
|
||||
else 1
|
||||
|
||||
def getServer(self):
|
||||
return self.server
|
||||
|
||||
|
||||
class Shortener:
|
||||
"""Some parts of this class was taken from
|
||||
python-yourls (https://github.com/tflink/python-yourls/) library
|
||||
"""
|
||||
def __init__(self, settings=None):
|
||||
self.api = settings['short_api']
|
||||
|
||||
if self.api is None:
|
||||
PBinCLIError("Unable to activate link shortener without short_api.")
|
||||
|
||||
# we checking which service is used, because some services doesn't require
|
||||
# any authentication, or have only one domain on which it working
|
||||
if self.api == 'yourls':
|
||||
self._yourls_init(settings)
|
||||
elif self.api == 'isgd' or self.api == 'vgd':
|
||||
self._gd_init()
|
||||
elif self.api == 'custom':
|
||||
self.apiurl = settings['short_url']
|
||||
|
||||
self.session, self.proxy = _config_requests(settings)
|
||||
|
||||
|
||||
def _yourls_init(self, settings):
|
||||
if not settings['short_url']:
|
||||
PBinCLIError("YOURLS: An API URL is required")
|
||||
|
||||
# setting API URL
|
||||
apiurl = settings['short_url']
|
||||
if apiurl.endswith('/yourls-api.php'):
|
||||
self.apiurl = apiurl
|
||||
elif apiurl.endswith('/'):
|
||||
self.apiurl = apiurl + 'yourls-api.php'
|
||||
else:
|
||||
PBinCLIError("YOURLS: Incorrect URL is provided.\n" +
|
||||
"It must contain full address to 'yourls-api.php' script (like https://example.com/yourls-api.php)\n" +
|
||||
"or just contain instance URL with '/' at the end (like https://example.com/)")
|
||||
|
||||
# validating for required credentials
|
||||
if settings['short_user'] and settings['short_pass'] and settings['short_token'] is None:
|
||||
self.auth_args = {'username': settings['short_user'], 'password': settings['short_pass']}
|
||||
elif settings['short_user'] is None and settings['short_pass'] is None and settings['short_token']:
|
||||
self.auth_args = {'signature': settings['short_token']}
|
||||
elif settings['short_user'] is None and settings['short_pass'] is None and settings['short_token'] is None:
|
||||
self.auth_args = {}
|
||||
else:
|
||||
PBinCLIError("YOURLS: either username and password or token are required. Otherwise set to default (None)")
|
||||
|
||||
|
||||
def _gd_init(self):
|
||||
if self.api == 'isgd':
|
||||
self.apiurl = 'https://is.gd/'
|
||||
else:
|
||||
self.apiurl = 'https://v.gd/'
|
||||
self.useragent = 'Mozilla/5.0 (compatible; pbincli - https://github.com/r4sas/pbincli/)'
|
||||
|
||||
|
||||
def getlink(self, url):
|
||||
# that is api -> function mapper for running service-related function when getlink() used
|
||||
servicesList = {
|
||||
'yourls': self._yourls,
|
||||
'clckru': self._clckru,
|
||||
'tinyurl': self._tinyurl,
|
||||
'isgd': self._gd,
|
||||
'vgd': self._gd,
|
||||
'cuttly': self._cuttly,
|
||||
'custom': self._custom
|
||||
}
|
||||
# run function selected by choosen API
|
||||
servicesList[self.api](url)
|
||||
|
||||
|
||||
def _yourls(self,url):
|
||||
request = {'action': 'shorturl', 'format': 'json', 'url': url}
|
||||
request.update(self.auth_args)
|
||||
|
||||
result = self.session.post(
|
||||
url = self.apiurl,
|
||||
proxies = self.proxy,
|
||||
data = request)
|
||||
|
||||
try:
|
||||
result.raise_for_status()
|
||||
except HTTPError:
|
||||
try:
|
||||
response = result.json()
|
||||
except ValueError:
|
||||
PBinCLIError("YOURLS: Unable parse response. Received (size = {}):\n{}".format(len(result.text), result.text))
|
||||
else:
|
||||
PBinCLIError("YOURLS: Received error from API: {} with JSON {}".format(result, response))
|
||||
else:
|
||||
response = result.json()
|
||||
|
||||
if {'status', 'statusCode', 'message'} <= set(response.keys()):
|
||||
if response['status'] == 'fail':
|
||||
PBinCLIError("YOURLS: Received error from API: {}".format(response['message']))
|
||||
if not 'shorturl' in response:
|
||||
PBinCLIError("YOURLS: Unknown error: {}".format(response['message']))
|
||||
else:
|
||||
print("Short Link:\t{}".format(response['shorturl']))
|
||||
else:
|
||||
PBinCLIError("YOURLS: No status, statusCode or message fields in response! Received:\n{}".format(response))
|
||||
|
||||
|
||||
def _clckru(self, url):
|
||||
request = {'url': url}
|
||||
|
||||
try:
|
||||
result = self.session.post(
|
||||
url = "https://clck.ru/--",
|
||||
proxies = self.proxy,
|
||||
data = request)
|
||||
print("Short Link:\t{}".format(result.text))
|
||||
except Exception as ex:
|
||||
PBinCLIError("clck.ru: unexcepted behavior: {}".format(ex))
|
||||
|
||||
|
||||
def _tinyurl(self, url):
|
||||
request = {'url': url}
|
||||
|
||||
try:
|
||||
result = self.session.post(
|
||||
url = "https://tinyurl.com/api-create.php",
|
||||
proxies = self.proxy,
|
||||
data = request)
|
||||
print("Short Link:\t{}".format(result.text))
|
||||
except Exception as ex:
|
||||
PBinCLIError("TinyURL: unexcepted behavior: {}".format(ex))
|
||||
|
||||
|
||||
def _gd(self, url):
|
||||
request = {
|
||||
'format': 'json',
|
||||
'url': url,
|
||||
'logstats': 0 # we don't want use any statistics
|
||||
}
|
||||
headers = { 'User-Agent': self.useragent}
|
||||
|
||||
try:
|
||||
result = self.session.post(
|
||||
url = self.apiurl + "create.php",
|
||||
headers = headers,
|
||||
proxies = self.proxy,
|
||||
data = request)
|
||||
|
||||
response = result.json()
|
||||
|
||||
if 'shorturl' in response:
|
||||
print("Short Link:\t{}".format(response['shorturl']))
|
||||
else:
|
||||
PBinCLIError("{}: got error {} from API: {}".format(
|
||||
"is.gd" if self.api == 'isgd' else 'v.gd',
|
||||
response['errorcode'],
|
||||
response['errormessage']))
|
||||
|
||||
except Exception as ex:
|
||||
PBinCLIError("{}: unexcepted behavior: {}".format(
|
||||
"is.gd" if self.api == 'isgd' else 'v.gd',
|
||||
ex))
|
||||
|
||||
|
||||
def _cuttly(self, url):
|
||||
request = {
|
||||
'url': url,
|
||||
'domain': 0
|
||||
}
|
||||
|
||||
try:
|
||||
result = self.session.post(
|
||||
url = "https://cutt.ly/scripts/shortenUrl.php",
|
||||
proxies = self.proxy,
|
||||
data = request)
|
||||
print("Short Link:\t{}".format(result.text))
|
||||
except Exception as ex:
|
||||
PBinCLIError("cutt.ly: unexcepted behavior: {}".format(ex))
|
||||
|
||||
|
||||
def _custom(self, url):
|
||||
if self.apiurl is None:
|
||||
PBinCLIError("No short_url specified - link will not be shortened.")
|
||||
|
||||
from urllib.parse import quote
|
||||
qUrl = quote(url, safe="") # urlencoded paste url
|
||||
rUrl = self.apiurl.replace("{{url}}", qUrl)
|
||||
|
||||
try:
|
||||
result = self.session.get(
|
||||
url = rUrl,
|
||||
proxies = self.proxy)
|
||||
print("Short Link:\t{}".format(result.text))
|
||||
except Exception as ex:
|
||||
PBinCLIError("Shorter: unexcepted behavior: {}".format(ex))
|
||||
|
155
pbincli/cli.py
155
pbincli/cli.py
@ -1,97 +1,164 @@
|
||||
#!/usr/bin/env python
|
||||
import os, sys, argparse
|
||||
from distutils.util import strtobool
|
||||
|
||||
import pbincli.actions
|
||||
from pbincli.api import PrivateBin
|
||||
from pbincli.utils import PBinCLIException
|
||||
from pbincli.utils import PBinCLIException, PBinCLIError, validate_url_ending
|
||||
|
||||
CONFIG_PATHS = [
|
||||
os.path.join(".", "pbincli.conf", ),
|
||||
os.path.join(os.getenv("HOME") or "~", ".config", "pbincli", "pbincli.conf")
|
||||
]
|
||||
|
||||
if sys.platform == "win32":
|
||||
CONFIG_PATHS.append(os.path.join(os.getenv("APPDATA"), "pbincli", "pbincli.conf"))
|
||||
elif sys.platform == "darwin":
|
||||
CONFIG_PATHS.append(os.path.join(os.getenv("HOME") or "~", "Library", "Application Support", "pbincli", "pbincli.conf"))
|
||||
|
||||
CONFIG_PATHS = [os.path.join(".", "pbincli.conf", ),
|
||||
os.path.join(os.getenv("HOME") or "~", ".config", "pbincli", "pbincli.conf") ]
|
||||
|
||||
def read_config(filename):
|
||||
"""Read config variables from a file"""
|
||||
settings = {}
|
||||
with open(filename) as f:
|
||||
for l in f.readlines():
|
||||
key, value = l.strip().split("=")
|
||||
settings[key.strip()] = value.strip()
|
||||
|
||||
if len(l.strip()) == 0:
|
||||
continue
|
||||
try:
|
||||
key, value = l.strip().split("=", 1)
|
||||
if value.strip().lower() in ['true', 'false']:
|
||||
settings[key.strip()] = bool(strtobool(value.strip()))
|
||||
else:
|
||||
settings[key.strip()] = value.strip()
|
||||
except ValueError:
|
||||
PBinCLIError("Unable to parse config file, please check it for errors.")
|
||||
return settings
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser = argparse.ArgumentParser(description='Full-featured PrivateBin command-line client')
|
||||
subparsers = parser.add_subparsers(title="actions", help="List of commands")
|
||||
|
||||
# a send command
|
||||
send_parser = subparsers.add_parser("send", description="Send data to PrivateBin instance")
|
||||
send_parser.add_argument("-t", "--text", help="text in quotes. Ignored if used stdin. If not used, forcefully used stdin")
|
||||
send_parser.add_argument("-f", "--file", help="example: image.jpg or full path to file")
|
||||
send_parser.add_argument("-p", "--password", help="password for encrypting paste")
|
||||
send_parser.add_argument("-t", "--text", help="Text in quotes. Ignored if used stdin. If not used, forcefully used stdin")
|
||||
send_parser.add_argument("-f", "--file", help="Example: image.jpg or full path to file")
|
||||
send_parser.add_argument("-p", "--password", help="Password for encrypting paste")
|
||||
send_parser.add_argument("-E", "--expire", default="1day", action="store",
|
||||
choices=["5min", "10min", "1hour", "1day", "1week", "1month", "1year", "never"], help="paste lifetime (default: 1day)")
|
||||
send_parser.add_argument("-B", "--burn", default=False, action="store_true", help="burn sent paste after reading")
|
||||
send_parser.add_argument("-D", "--discus", default=False, action="store_true", help="open discussion for sent paste")
|
||||
choices=["5min", "10min", "1hour", "1day", "1week", "1month", "1year", "never"], help="Paste lifetime (default: 1day)")
|
||||
send_parser.add_argument("-B", "--burn", default=argparse.SUPPRESS, action="store_true", help="Set \"Burn after reading\" flag")
|
||||
send_parser.add_argument("-D", "--discus", default=argparse.SUPPRESS, action="store_true", help="Open discussion for sent paste")
|
||||
send_parser.add_argument("-F", "--format", default="plaintext", action="store",
|
||||
choices=["plaintext", "syntaxhighlighting", "markdown"], help="format of text (default: plaintext)")
|
||||
send_parser.add_argument("-q", "--notext", default=False, action="store_true", help="don't send text in paste")
|
||||
choices=["plaintext", "syntaxhighlighting", "markdown"], help="Format of text (default: plaintext)")
|
||||
send_parser.add_argument("-q", "--notext", default=False, action="store_true", help="Don't send text in paste")
|
||||
send_parser.add_argument("-c", "--compression", default="zlib", action="store",
|
||||
choices=["zlib", "none"], help="set compression for paste (default: zlib). Note: works only on v2 paste format")
|
||||
send_parser.add_argument("--no-check-certificate", default=True, action="store_false", help="disable certificate validation")
|
||||
send_parser.add_argument("--no-insecure-warning", default=False, action="store_true", help="suppress InsecureRequestWarning (only with --no-check-certificate)")
|
||||
send_parser.add_argument("-d", "--debug", default=False, action="store_true", help="enable debug")
|
||||
send_parser.add_argument("--dry", default=False, action="store_true", help="invoke dry run")
|
||||
send_parser.add_argument("stdin", help="input paste text from stdin", nargs="?", type=argparse.FileType("r"), default=sys.stdin)
|
||||
choices=["zlib", "none"], help="Set compression for paste (default: zlib). Note: works only on v2 paste format")
|
||||
## URL shortener
|
||||
send_parser.add_argument("-S", "--short", default=argparse.SUPPRESS, action="store_true", help="Use URL shortener")
|
||||
send_parser.add_argument("--short-api", default=argparse.SUPPRESS, action="store",
|
||||
choices=["tinyurl", "clckru", "isgd", "vgd", "cuttly", "yourls", "custom"], help="API used by shortener service")
|
||||
send_parser.add_argument("--short-url", default=argparse.SUPPRESS, help="URL of shortener service API")
|
||||
send_parser.add_argument("--short-user", default=argparse.SUPPRESS, help="Shortener username")
|
||||
send_parser.add_argument("--short-pass", default=argparse.SUPPRESS, help="Shortener password")
|
||||
send_parser.add_argument("--short-token", default=argparse.SUPPRESS, help="Shortener token")
|
||||
## Connection options
|
||||
send_parser.add_argument("-s", "--server", default=argparse.SUPPRESS, help="Instance URL (default: https://paste.i2pd.xyz/)")
|
||||
send_parser.add_argument("-x", "--proxy", default=argparse.SUPPRESS, help="Proxy server address (default: None)")
|
||||
send_parser.add_argument("--no-check-certificate", default=argparse.SUPPRESS, action="store_true", help="Disable certificate validation")
|
||||
send_parser.add_argument("--no-insecure-warning", default=argparse.SUPPRESS, action="store_true",
|
||||
help="Suppress InsecureRequestWarning (only with --no-check-certificate)")
|
||||
##
|
||||
send_parser.add_argument("-L", "--mirrors", default=argparse.SUPPRESS, help="Comma-separated list of mirrors of service with scheme (default: None)")
|
||||
send_parser.add_argument("-v", "--verbose", default=False, action="store_true", help="Enable verbose output")
|
||||
send_parser.add_argument("-d", "--debug", default=False, action="store_true", help="Enable debug output")
|
||||
send_parser.add_argument("--dry", default=False, action="store_true", help="Invoke dry run")
|
||||
send_parser.add_argument("stdin", help="Input paste text from stdin", nargs="?", type=argparse.FileType("r"), default=sys.stdin)
|
||||
send_parser.set_defaults(func=pbincli.actions.send)
|
||||
|
||||
# a get command
|
||||
get_parser = subparsers.add_parser("get", description="Get data from PrivateBin instance")
|
||||
get_parser.add_argument("pasteinfo", help="example: aabb#cccddd")
|
||||
get_parser.add_argument("-p", "--password", help="password for decrypting paste")
|
||||
get_parser.add_argument("--no-check-certificate", default=True, action="store_false", help="disable certificate validation")
|
||||
get_parser.add_argument("--no-insecure-warning", default=False, action="store_true", help="suppress InsecureRequestWarning (only with --no-check-certificate)")
|
||||
get_parser.add_argument("-d", "--debug", default=False, action="store_true", help="enable debug")
|
||||
get_parser.add_argument("pasteinfo", help="\"PasteID#Passphrase\" or full URL")
|
||||
get_parser.add_argument("-p", "--password", help="Password for decrypting paste")
|
||||
## Connection options
|
||||
get_parser.add_argument("-s", "--server", default=argparse.SUPPRESS, help="Instance URL (default: https://paste.i2pd.xyz/, ignored if URL used in pasteinfo)")
|
||||
get_parser.add_argument("-x", "--proxy", default=argparse.SUPPRESS, help="Proxy server address (default: None)")
|
||||
get_parser.add_argument("--no-check-certificate", default=argparse.SUPPRESS, action="store_true", help="Disable certificate validation")
|
||||
get_parser.add_argument("--no-insecure-warning", default=argparse.SUPPRESS, action="store_true",
|
||||
help="Suppress InsecureRequestWarning (only with --no-check-certificate)")
|
||||
##
|
||||
get_parser.add_argument("-v", "--verbose", default=False, action="store_true", help="Enable verbose output")
|
||||
get_parser.add_argument("-d", "--debug", default=False, action="store_true", help="Enable debug output")
|
||||
get_parser.set_defaults(func=pbincli.actions.get)
|
||||
|
||||
# a delete command
|
||||
delete_parser = subparsers.add_parser("delete", description="Delete paste from PrivateBin instance using token")
|
||||
delete_parser.add_argument("-p", "--paste", required=True, help="paste id")
|
||||
delete_parser.add_argument("-t", "--token", required=True, help="paste deletion token")
|
||||
delete_parser.add_argument("--no-check-certificate", default=True, action="store_false", help="disable certificate validation")
|
||||
delete_parser.add_argument("--no-insecure-warning", default=False, action="store_true", help="suppress InsecureRequestWarning (only with --no-check-certificate)")
|
||||
delete_parser.add_argument("-d", "--debug", default=False, action="store_true", help="enable debug")
|
||||
delete_parser = subparsers.add_parser("delete", description="Delete paste from PrivateBin instance")
|
||||
delete_parser.add_argument("pasteinfo", help="Paste deletion URL or string in \"pasteid=PasteID&deletetoken=Token\" format")
|
||||
## Connection options
|
||||
delete_parser.add_argument("-s", "--server", default=argparse.SUPPRESS, help="Instance URL (default: https://paste.i2pd.xyz/)")
|
||||
delete_parser.add_argument("-x", "--proxy", default=argparse.SUPPRESS, help="Proxy server address (default: None)")
|
||||
delete_parser.add_argument("--no-check-certificate", default=argparse.SUPPRESS, action="store_true", help="Disable certificate validation")
|
||||
delete_parser.add_argument("--no-insecure-warning", default=argparse.SUPPRESS, action="store_true",
|
||||
help="Suppress InsecureRequestWarning (only with --no-check-certificate)")
|
||||
##
|
||||
delete_parser.add_argument("-v", "--verbose", default=False, action="store_true", help="Enable verbose output")
|
||||
delete_parser.add_argument("-d", "--debug", default=False, action="store_true", help="Enable debug output")
|
||||
delete_parser.set_defaults(func=pbincli.actions.delete)
|
||||
|
||||
# parse arguments
|
||||
args = parser.parse_args()
|
||||
|
||||
# default configuration
|
||||
CONFIG = {
|
||||
"server": "https://paste.i2pd.xyz/",
|
||||
"proxy": None
|
||||
'server': 'https://paste.i2pd.xyz/',
|
||||
'mirrors': None,
|
||||
'proxy': None,
|
||||
'expire': '1day',
|
||||
'burn': False,
|
||||
'discus': False,
|
||||
'format': None,
|
||||
'short': False,
|
||||
'short_api': None,
|
||||
'short_url': None,
|
||||
'short_user': None,
|
||||
'short_pass': None,
|
||||
'short_token': None,
|
||||
'no_check_certificate': False,
|
||||
'no_insecure_warning': False,
|
||||
'compression': None
|
||||
}
|
||||
|
||||
# Configuration preference order:
|
||||
# 1. Command line switches
|
||||
# 2. Environment variables
|
||||
# 3. Configuration file
|
||||
# 4. Defaults above
|
||||
|
||||
for p in CONFIG_PATHS:
|
||||
if os.path.exists(p):
|
||||
CONFIG.update(read_config(p))
|
||||
fileconfig = read_config(p)
|
||||
if args.debug: print("Configuration readed from file:\t{}".format(fileconfig))
|
||||
CONFIG.update(fileconfig)
|
||||
break
|
||||
|
||||
for key in CONFIG.keys():
|
||||
var = "PRIVATEBIN_{}".format(key.upper())
|
||||
if var in os.environ: CONFIG[key] = os.getenv(var)
|
||||
# values from command line switches are preferred
|
||||
args_var = vars(args)
|
||||
if key in args_var:
|
||||
CONFIG[key] = args_var[key]
|
||||
|
||||
SETTINGS = {
|
||||
"proxy": CONFIG["proxy"],
|
||||
"nocheckcert": args.no_check_certificate,
|
||||
"noinsecurewarn": args.no_insecure_warning
|
||||
}
|
||||
# Re-validate PrivateBin instance URL
|
||||
CONFIG['server'] = validate_url_ending(CONFIG['server'])
|
||||
|
||||
api_client = PrivateBin(CONFIG["server"], settings=SETTINGS)
|
||||
if args.debug: print("Whole configuration:\t\t{}".format(CONFIG))
|
||||
api_client = PrivateBin(CONFIG)
|
||||
|
||||
if hasattr(args, "func"):
|
||||
try:
|
||||
args.func(args, api_client)
|
||||
args.func(args, api_client, settings=CONFIG)
|
||||
except PBinCLIException as pe:
|
||||
print("PBinCLI error: {}".format(pe))
|
||||
sys.exit(1)
|
||||
raise PBinCLIException("error: {}".format(pe))
|
||||
else:
|
||||
parser.print_help()
|
||||
|
||||
|
@ -1,15 +1,33 @@
|
||||
from Crypto.Random import get_random_bytes
|
||||
from Crypto.Cipher import AES
|
||||
from base64 import b64encode, b64decode
|
||||
from pbincli.utils import PBinCLIException
|
||||
from pbincli.utils import PBinCLIError
|
||||
import zlib
|
||||
|
||||
# try import AES cipher and check if it has GCM mode (prevent usage of pycrypto)
|
||||
try:
|
||||
from Crypto.Cipher import AES
|
||||
if not hasattr(AES, 'MODE_GCM'):
|
||||
try:
|
||||
from Cryptodome.Cipher import AES
|
||||
from Cryptodome.Random import get_random_bytes
|
||||
except ImportError:
|
||||
PBinCLIError("AES GCM mode is not found in imported crypto module.\n" +
|
||||
"That can happen if you have installed pycrypto.\n\n" +
|
||||
"We tried to import pycryptodomex but it is not available.\n" +
|
||||
"Please install it via pip, if you still need pycrypto, by running:\n" +
|
||||
"\tpip install pycryptodomex\n" +
|
||||
"... otherwise use separate python environment or uninstall pycrypto:\n" +
|
||||
"\tpip uninstall pycrypto")
|
||||
else:
|
||||
from Crypto.Random import get_random_bytes
|
||||
except ImportError:
|
||||
PBinCLIError("Unable import pycryptodome")
|
||||
|
||||
|
||||
CIPHER_ITERATION_COUNT = 100000
|
||||
CIPHER_SALT_BYTES = 8
|
||||
CIPHER_BLOCK_BITS = 256
|
||||
CIPHER_BLOCK_BYTES = int(CIPHER_BLOCK_BITS/8)
|
||||
CIPHER_TAG_BITS = int(CIPHER_BLOCK_BITS/2)
|
||||
CIPHER_TAG_BYTES = int(CIPHER_TAG_BITS/8)
|
||||
CIPHER_TAG_BITS = 128
|
||||
|
||||
|
||||
class Paste:
|
||||
def __init__(self, debug=False):
|
||||
@ -19,9 +37,13 @@ class Paste:
|
||||
self._text = ''
|
||||
self._attachment = ''
|
||||
self._attachment_name = ''
|
||||
self._key = get_random_bytes(CIPHER_BLOCK_BYTES)
|
||||
self._password = ''
|
||||
self._debug = debug
|
||||
self._iteration_count = CIPHER_ITERATION_COUNT
|
||||
self._salt_bytes = CIPHER_SALT_BYTES
|
||||
self._block_bits = CIPHER_BLOCK_BITS
|
||||
self._tag_bits = CIPHER_TAG_BITS
|
||||
self._key = get_random_bytes(int(self._block_bits / 8))
|
||||
|
||||
|
||||
def setVersion(self, version):
|
||||
@ -105,8 +127,8 @@ class Paste:
|
||||
return PBKDF2(
|
||||
self._key + self._password.encode(),
|
||||
salt,
|
||||
dkLen = CIPHER_BLOCK_BYTES,
|
||||
count = CIPHER_ITERATION_COUNT,
|
||||
dkLen = int(self._block_bits / 8),
|
||||
count = self._iteration_count,
|
||||
prf = lambda password, salt: HMAC.new(
|
||||
password,
|
||||
salt,
|
||||
@ -115,10 +137,10 @@ class Paste:
|
||||
|
||||
|
||||
@classmethod
|
||||
def __initializeCipher(self, key, iv, adata):
|
||||
def __initializeCipher(self, key, iv, adata, tagsize):
|
||||
from pbincli.utils import json_encode
|
||||
|
||||
cipher = AES.new(key, AES.MODE_GCM, nonce=iv, mac_len=CIPHER_TAG_BYTES)
|
||||
cipher = AES.new(key, AES.MODE_GCM, nonce=iv, mac_len=tagsize)
|
||||
cipher.update(json_encode(adata))
|
||||
return cipher
|
||||
|
||||
@ -143,7 +165,7 @@ class Paste:
|
||||
elif self._version == 1:
|
||||
return zlib.decompress(bytearray(map(lambda c:ord(c)&255, b64decode(s.encode('utf-8')).decode('utf-8'))), -zlib.MAX_WBITS)
|
||||
else:
|
||||
raise PBinCLIException('Unknown compression type provided in paste!')
|
||||
PBinCLIError('Unknown compression type provided in paste!')
|
||||
|
||||
|
||||
def __compress(self, s):
|
||||
@ -160,115 +182,146 @@ class Paste:
|
||||
b = co.compress(s) + co.flush()
|
||||
return b64encode(''.join(map(chr, b)).encode('utf-8'))
|
||||
else:
|
||||
raise PBinCLIException('Unknown compression type provided!')
|
||||
PBinCLIError('Unknown compression type provided!')
|
||||
|
||||
|
||||
def decrypt(self):
|
||||
# that is wrapper which running needed function regrading to paste version
|
||||
if self._version == 2: self._decryptV2()
|
||||
else: self._decryptV1()
|
||||
|
||||
|
||||
def _decryptV2(self):
|
||||
from json import loads as json_decode
|
||||
iv = b64decode(self._data['adata'][0][0])
|
||||
salt = b64decode(self._data['adata'][0][1])
|
||||
|
||||
self._iteration_count = self._data['adata'][0][2]
|
||||
self._block_bits = self._data['adata'][0][3]
|
||||
self._tag_bits = self._data['adata'][0][4]
|
||||
cipher_tag_bytes = int(self._tag_bits / 8)
|
||||
|
||||
key = self.__deriveKey(salt)
|
||||
|
||||
# Get compression type from received paste
|
||||
self._compression = self._data['adata'][0][7]
|
||||
|
||||
cipher = self.__initializeCipher(key, iv, self._data['adata'], cipher_tag_bytes)
|
||||
# Cut the cipher text into message and tag
|
||||
cipher_text_tag = b64decode(self._data['ct'])
|
||||
cipher_text = cipher_text_tag[:-cipher_tag_bytes]
|
||||
cipher_tag = cipher_text_tag[-cipher_tag_bytes:]
|
||||
cipher_message = json_decode(self.__decompress(cipher.decrypt_and_verify(cipher_text, cipher_tag)).decode())
|
||||
|
||||
self._text = cipher_message['paste'].encode()
|
||||
|
||||
if 'attachment' in cipher_message and 'attachment_name' in cipher_message:
|
||||
self._attachment = cipher_message['attachment']
|
||||
self._attachment_name = cipher_message['attachment_name']
|
||||
|
||||
|
||||
def _decryptV1(self):
|
||||
from sjcl import SJCL
|
||||
from json import loads as json_decode
|
||||
|
||||
if self._version == 2:
|
||||
iv = b64decode(self._data['adata'][0][0])
|
||||
salt = b64decode(self._data['adata'][0][1])
|
||||
key = self.__deriveKey(salt)
|
||||
password = self.__preparePassKey()
|
||||
cipher_text = json_decode(self._data['data'])
|
||||
if self._debug: print("Text:\t{}\n".format(cipher_text))
|
||||
|
||||
# Get compression type from received paste
|
||||
self._compression = self._data['adata'][0][7]
|
||||
text = SJCL().decrypt(cipher_text, password)
|
||||
|
||||
cipher = self.__initializeCipher(key, iv, self._data['adata'])
|
||||
# Cut the cipher text into message and tag
|
||||
cipher_text_tag = b64decode(self._data['ct'])
|
||||
cipher_text = cipher_text_tag[:-CIPHER_TAG_BYTES]
|
||||
cipher_tag = cipher_text_tag[-CIPHER_TAG_BYTES:]
|
||||
cipher_message = json_decode(self.__decompress(cipher.decrypt_and_verify(cipher_text, cipher_tag)).decode())
|
||||
if len(text):
|
||||
if self._debug: print("Decoded Text:\t{}\n".format(text))
|
||||
self._text = self.__decompress(text.decode())
|
||||
|
||||
self._text = cipher_message['paste'].encode()
|
||||
if 'attachment' in self._data and 'attachmentname' in self._data:
|
||||
cipherfile = json_decode(self._data['attachment'])
|
||||
cipherfilename = json_decode(self._data['attachmentname'])
|
||||
|
||||
if 'attachment' in cipher_message and 'attachment_name' in cipher_message:
|
||||
self._attachment = cipher_message['attachment']
|
||||
self._attachment_name = cipher_message['attachment_name']
|
||||
else:
|
||||
from sjcl import SJCL
|
||||
if self._debug: print("Name:\t{}\nData:\t{}".format(cipherfilename, cipherfile))
|
||||
|
||||
password = self.__preparePassKey()
|
||||
attachment = SJCL().decrypt(cipherfile, password)
|
||||
attachmentname = SJCL().decrypt(cipherfilename, password)
|
||||
|
||||
cipher_text = json_decode(self._data['data'])
|
||||
|
||||
if self._debug: print("Text:\t{}\n".format(cipher_text))
|
||||
|
||||
text = SJCL().decrypt(cipher_text, password)
|
||||
|
||||
if len(text):
|
||||
if self._debug: print("Decoded Text:\t{}\n".format(text))
|
||||
self._text = self.__decompress(text.decode())
|
||||
|
||||
if 'attachment' in self._data and 'attachmentname' in self._data:
|
||||
cipherfile = json_decode(self._data['attachment'])
|
||||
cipherfilename = json_decode(self._data['attachmentname'])
|
||||
|
||||
if self._debug: print("Name:\t{}\nData:\t{}".format(cipherfilename, cipherfile))
|
||||
|
||||
attachment = SJCL().decrypt(cipherfile, password)
|
||||
attachmentname = SJCL().decrypt(cipherfilename, password)
|
||||
|
||||
self._attachment = self.__decompress(attachment.decode('utf-8')).decode('utf-8')
|
||||
self._attachment_name = self.__decompress(attachmentname.decode('utf-8')).decode('utf-8')
|
||||
self._attachment = self.__decompress(attachment.decode('utf-8')).decode('utf-8')
|
||||
self._attachment_name = self.__decompress(attachmentname.decode('utf-8')).decode('utf-8')
|
||||
|
||||
|
||||
def encrypt(self, formatter, burnafterreading, discussion, expiration):
|
||||
# that is wrapper which running needed function regrading to paste version
|
||||
self._formatter = formatter
|
||||
self._burnafterreading = burnafterreading
|
||||
self._discussion = discussion
|
||||
self._expiration = expiration
|
||||
|
||||
if self._debug: print("[Enc] Starting encyptor…")
|
||||
if self._version == 2: self._encryptV2()
|
||||
else: self._encryptV1()
|
||||
|
||||
|
||||
def _encryptV2(self):
|
||||
from pbincli.utils import json_encode
|
||||
if self._version == 2:
|
||||
iv = get_random_bytes(CIPHER_TAG_BYTES)
|
||||
salt = get_random_bytes(CIPHER_SALT_BYTES)
|
||||
key = self.__deriveKey(salt)
|
||||
|
||||
# prepare encryption authenticated data and message
|
||||
adata = [
|
||||
[
|
||||
b64encode(iv).decode(),
|
||||
b64encode(salt).decode(),
|
||||
CIPHER_ITERATION_COUNT,
|
||||
CIPHER_BLOCK_BITS,
|
||||
CIPHER_TAG_BITS,
|
||||
'aes',
|
||||
'gcm',
|
||||
self._compression
|
||||
],
|
||||
formatter,
|
||||
int(burnafterreading),
|
||||
int(discussion)
|
||||
]
|
||||
cipher_message = {'paste':self._text}
|
||||
if self._attachment:
|
||||
cipher_message['attachment'] = self._attachment
|
||||
cipher_message['attachment_name'] = self._attachment_name
|
||||
if self._debug: print("[Enc] Preparing IV, Salt…")
|
||||
iv = get_random_bytes(int(self._tag_bits / 8))
|
||||
salt = get_random_bytes(self._salt_bytes)
|
||||
if self._debug: print("[Enc] Deriving Key…")
|
||||
key = self.__deriveKey(salt)
|
||||
|
||||
cipher = self.__initializeCipher(key, iv, adata)
|
||||
ciphertext, tag = cipher.encrypt_and_digest(self.__compress(json_encode(cipher_message)))
|
||||
if self._debug: print("[Enc] Preparing aData and message…")
|
||||
# prepare encryption authenticated data and message
|
||||
adata = [
|
||||
[
|
||||
b64encode(iv).decode(),
|
||||
b64encode(salt).decode(),
|
||||
self._iteration_count,
|
||||
self._block_bits,
|
||||
self._tag_bits,
|
||||
'aes',
|
||||
'gcm',
|
||||
self._compression
|
||||
],
|
||||
self._formatter,
|
||||
int(self._discussion),
|
||||
int(self._burnafterreading)
|
||||
]
|
||||
cipher_message = {'paste':self._text}
|
||||
if self._attachment:
|
||||
cipher_message['attachment'] = self._attachment
|
||||
cipher_message['attachment_name'] = self._attachment_name
|
||||
|
||||
self._data = {'v':2,'adata':adata,'ct':b64encode(ciphertext + tag).decode(),'meta':{'expire':expiration}}
|
||||
if self._debug: print("[Enc] Encrypting message…")
|
||||
cipher = self.__initializeCipher(key, iv, adata, int(self._tag_bits /8 ))
|
||||
ciphertext, tag = cipher.encrypt_and_digest(self.__compress(json_encode(cipher_message)))
|
||||
|
||||
else:
|
||||
from sjcl import SJCL
|
||||
if self._debug: print("PBKDF2 Key:\t{}\nCipherText:\t{}\nCipherTag:\t{}"
|
||||
.format(b64encode(key), b64encode(ciphertext), b64encode(tag)))
|
||||
|
||||
self._data = {'expire':expiration,'formatter':formatter,'burnafterreading':int(burnafterreading),'opendiscussion':int(discussion)}
|
||||
self._data = {'v':2,'adata':adata,'ct':b64encode(ciphertext + tag).decode(),'meta':{'expire':self._expiration}}
|
||||
|
||||
password = self.__preparePassKey()
|
||||
|
||||
if self._debug: print("Password:\t{}".format(password))
|
||||
def _encryptV1(self):
|
||||
from sjcl import SJCL
|
||||
from pbincli.utils import json_encode
|
||||
|
||||
# Encrypting text
|
||||
cipher = SJCL().encrypt(self.__compress(self._text.encode('utf-8')), password, mode='gcm')
|
||||
for k in ['salt', 'iv', 'ct']: cipher[k] = cipher[k].decode()
|
||||
self._data = {'expire':self._expiration,'formatter':self._formatter,
|
||||
'burnafterreading':int(self._burnafterreading),'opendiscussion':int(self._discussion)}
|
||||
|
||||
self._data['data'] = json_encode(cipher)
|
||||
password = self.__preparePassKey()
|
||||
if self._debug: print("Password:\t{}".format(password))
|
||||
|
||||
if self._attachment:
|
||||
cipherfile = SJCL().encrypt(self.__compress(self._attachment.encode('utf-8')), password, mode='gcm')
|
||||
for k in ['salt', 'iv', 'ct']: cipherfile[k] = cipherfile[k].decode()
|
||||
# Encrypting text
|
||||
cipher = SJCL().encrypt(self.__compress(self._text.encode('utf-8')), password, mode='gcm')
|
||||
for k in ['salt', 'iv', 'ct']: cipher[k] = cipher[k].decode()
|
||||
|
||||
cipherfilename = SJCL().encrypt(self.__compress(self._attachment_name.encode('utf-8')), password, mode='gcm')
|
||||
for k in ['salt', 'iv', 'ct']: cipherfilename[k] = cipherfilename[k].decode()
|
||||
self._data['data'] = json_encode(cipher)
|
||||
|
||||
self._data['attachment'] = json_encode(cipherfile)
|
||||
self._data['attachmentname'] = json_encode(cipherfilename)
|
||||
if self._attachment:
|
||||
cipherfile = SJCL().encrypt(self.__compress(self._attachment.encode('utf-8')), password, mode='gcm')
|
||||
for k in ['salt', 'iv', 'ct']: cipherfile[k] = cipherfile[k].decode()
|
||||
|
||||
cipherfilename = SJCL().encrypt(self.__compress(self._attachment_name.encode('utf-8')), password, mode='gcm')
|
||||
for k in ['salt', 'iv', 'ct']: cipherfilename[k] = cipherfilename[k].decode()
|
||||
|
||||
self._data['attachment'] = json_encode(cipherfile)
|
||||
self._data['attachmentname'] = json_encode(cipherfilename)
|
||||
|
@ -1,9 +1,14 @@
|
||||
import json, ntpath, os
|
||||
import json, ntpath, os, sys
|
||||
|
||||
class PBinCLIException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def PBinCLIError(message):
|
||||
print("PBinCLI Error: {}".format(message), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def path_leaf(path):
|
||||
head, tail = ntpath.split(path)
|
||||
return tail or ntpath.basename(head)
|
||||
@ -12,14 +17,29 @@ def path_leaf(path):
|
||||
def check_readable(f):
|
||||
# Checks if path exists and readable
|
||||
if not os.path.exists(f) or not os.access(f, os.R_OK):
|
||||
raise PBinCLIException("Error accessing path: {}".format(f))
|
||||
PBinCLIError("Error accessing path: {}".format(f))
|
||||
|
||||
|
||||
def check_writable(f):
|
||||
# Checks if path is writable
|
||||
if not os.access(os.path.dirname(f) or ".", os.W_OK):
|
||||
raise PBinCLIException("Path is not writable: {}".format(f))
|
||||
PBinCLIError("Path is not writable: {}".format(f))
|
||||
|
||||
|
||||
def json_encode(s):
|
||||
return json.dumps(s, separators=(',',':')).encode()
|
||||
|
||||
|
||||
def validate_url_ending(s):
|
||||
if not s.endswith('/'):
|
||||
s = s + "/"
|
||||
return s
|
||||
|
||||
def uri_validator(x):
|
||||
from urllib.parse import urlsplit
|
||||
try:
|
||||
result = urlsplit(x)
|
||||
isuri = all([result.scheme, result.netloc])
|
||||
return result, isuri
|
||||
except ValueError:
|
||||
return False
|
2
pyproject.toml
Normal file
2
pyproject.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[build-system]
|
||||
requires = ["setuptools", "wheel"]
|
9
setup.py
9
setup.py
@ -17,7 +17,7 @@ setup(
|
||||
long_description_content_type='text/x-rst',
|
||||
author='R4SAS',
|
||||
author_email='r4sas@i2pmail.org',
|
||||
url='https://github.com/r4sas/PBinCLI',
|
||||
url='https://github.com/r4sas/PBinCLI/',
|
||||
keywords='privatebin cryptography security',
|
||||
license='MIT',
|
||||
classifiers=[
|
||||
@ -31,9 +31,14 @@ setup(
|
||||
],
|
||||
packages=['pbincli'],
|
||||
install_requires=install_requires,
|
||||
python_requires='>=3',
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'pbincli=pbincli.cli:main',
|
||||
],
|
||||
}
|
||||
},
|
||||
project_urls={
|
||||
'Bug Reports': 'https://github.com/r4sas/PBinCLI/issues',
|
||||
'Source': 'https://github.com/r4sas/PBinCLI/',
|
||||
},
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user