2020-08-10 11:48:47 +03:00
|
|
|
#!/usr/bin/env python3
|
2015-09-18 18:36:14 +03:00
|
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
Main script including runserver and delete-paste.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import sys
|
|
|
|
import re
|
2020-08-13 15:36:42 +03:00
|
|
|
import os
|
|
|
|
|
2020-08-14 19:18:17 +03:00
|
|
|
from distutils.util import strtobool
|
|
|
|
|
2020-08-13 15:36:42 +03:00
|
|
|
import zerobin
|
2015-09-18 18:36:14 +03:00
|
|
|
|
2020-08-12 10:19:38 +03:00
|
|
|
from zerobin.utils import (
|
|
|
|
settings,
|
|
|
|
SettingsValidationError,
|
2020-08-13 15:36:42 +03:00
|
|
|
ensure_app_context,
|
2020-08-12 10:19:38 +03:00
|
|
|
hash_password,
|
|
|
|
)
|
2015-09-18 18:36:14 +03:00
|
|
|
from zerobin.routes import get_app
|
|
|
|
from zerobin.paste import Paste
|
|
|
|
|
2020-08-13 15:36:42 +03:00
|
|
|
|
2015-09-18 18:36:14 +03:00
|
|
|
from bottle import run
|
|
|
|
|
|
|
|
import clize
|
|
|
|
|
2020-08-10 11:48:47 +03:00
|
|
|
|
|
|
|
def runserver(
|
2020-08-11 17:37:03 +03:00
|
|
|
*,
|
2020-08-10 11:48:47 +03:00
|
|
|
host="",
|
|
|
|
port="",
|
2020-08-13 15:36:42 +03:00
|
|
|
config_dir="",
|
|
|
|
data_dir="",
|
2020-08-10 11:48:47 +03:00
|
|
|
debug=None,
|
|
|
|
version=False,
|
2020-08-12 18:26:21 +03:00
|
|
|
server="paste",
|
2020-08-10 11:48:47 +03:00
|
|
|
):
|
2015-09-18 18:36:14 +03:00
|
|
|
if version:
|
2020-08-10 11:48:47 +03:00
|
|
|
print("0bin V%s" % settings.VERSION)
|
2015-09-18 18:36:14 +03:00
|
|
|
sys.exit(0)
|
|
|
|
|
|
|
|
try:
|
2020-08-14 19:18:17 +03:00
|
|
|
if debug is not None:
|
|
|
|
debug = strtobool(debug)
|
2020-08-14 17:41:45 +03:00
|
|
|
updated_settings, app = get_app(
|
|
|
|
debug=debug, config_dir=config_dir, data_dir=data_dir,
|
|
|
|
)
|
2015-09-18 18:36:14 +03:00
|
|
|
except SettingsValidationError as err:
|
2020-08-14 17:41:45 +03:00
|
|
|
print("Configuration error: %s" % err, file=sys.stderr)
|
2015-09-18 18:36:14 +03:00
|
|
|
sys.exit(1)
|
|
|
|
|
2020-08-14 17:41:45 +03:00
|
|
|
updated_settings.HOST = host or os.environ.get(
|
|
|
|
"ZEROBIN_HOST", updated_settings.HOST
|
|
|
|
)
|
|
|
|
updated_settings.PORT = port or os.environ.get(
|
|
|
|
"ZEROBIN_PORT", updated_settings.PORT
|
|
|
|
)
|
2020-08-13 15:36:42 +03:00
|
|
|
|
2020-08-14 17:41:45 +03:00
|
|
|
if updated_settings.DEBUG:
|
2020-08-14 17:45:06 +03:00
|
|
|
print(
|
|
|
|
f"Admin URL for dev: http://{updated_settings.HOST}:{updated_settings.PORT}{settings.ADMIN_URL}"
|
|
|
|
)
|
2020-08-13 15:36:42 +03:00
|
|
|
print()
|
2020-08-10 11:48:47 +03:00
|
|
|
run(
|
2020-08-14 17:41:45 +03:00
|
|
|
app,
|
|
|
|
host=updated_settings.HOST,
|
|
|
|
port=updated_settings.PORT,
|
|
|
|
reloader=True,
|
|
|
|
server=server,
|
2020-08-10 11:48:47 +03:00
|
|
|
)
|
2015-09-18 18:36:14 +03:00
|
|
|
else:
|
2020-08-14 17:41:45 +03:00
|
|
|
run(app, host=settings.HOST, port=updated_settings.PORT, server=server)
|
2015-09-18 18:36:14 +03:00
|
|
|
|
|
|
|
|
|
|
|
# The regex parse the url and separate the paste's id from the decription key
|
|
|
|
# After the '/paste/' part, there is several caracters, identified as
|
|
|
|
# the uuid of the paste. Followed by a '#', the decryption key of the paste.
|
2020-08-10 11:48:47 +03:00
|
|
|
paste_url = re.compile("/paste/(?P<paste_id>.*)#(?P<key>.*)")
|
|
|
|
|
2015-09-18 18:36:14 +03:00
|
|
|
|
|
|
|
def unpack_paste(paste):
|
|
|
|
"""Take either the ID or the URL of a paste, and return its ID"""
|
|
|
|
|
|
|
|
try_url = paste_url.search(paste)
|
|
|
|
|
|
|
|
if try_url:
|
2020-08-10 11:48:47 +03:00
|
|
|
return try_url.group("paste_id")
|
2015-09-18 18:36:14 +03:00
|
|
|
return paste
|
|
|
|
|
2020-08-10 11:48:47 +03:00
|
|
|
|
2020-08-11 17:37:03 +03:00
|
|
|
def delete_paste(*pastes, quiet=False):
|
2015-09-18 18:36:14 +03:00
|
|
|
"""
|
|
|
|
Remove pastes, given its ID or its URL
|
|
|
|
|
|
|
|
quiet: Don't print anything
|
|
|
|
|
|
|
|
pastes: List of pastes, given by ID or URL
|
|
|
|
"""
|
|
|
|
|
|
|
|
for paste_uuid in map(unpack_paste, pastes):
|
|
|
|
try:
|
|
|
|
Paste.load(paste_uuid).delete()
|
|
|
|
|
|
|
|
if not quiet:
|
2020-08-10 11:48:47 +03:00
|
|
|
print("Paste {} is removed".format(paste_uuid))
|
|
|
|
|
2015-09-18 18:36:14 +03:00
|
|
|
except ValueError:
|
|
|
|
if not quiet:
|
2020-08-10 11:48:47 +03:00
|
|
|
print("Paste {} doesn't exist".format(paste_uuid))
|
|
|
|
|
2015-09-18 18:36:14 +03:00
|
|
|
|
2020-08-13 15:36:42 +03:00
|
|
|
def infos():
|
2020-08-12 10:19:38 +03:00
|
|
|
""" Print the route to the 0bin admin.
|
|
|
|
|
|
|
|
The admin route is generated by zerobin so that bots won't easily
|
|
|
|
bruteforce it. To get the full URL, simply preppend your website domain
|
|
|
|
name to it.
|
|
|
|
|
|
|
|
E.G:
|
|
|
|
|
|
|
|
If this command prints:
|
|
|
|
|
|
|
|
"/admin/f1cc3972a4b933c734b37906940cf69886161492ee4eb7c1faff5d7b5e92efb8"
|
|
|
|
|
|
|
|
Then the admin url is:
|
|
|
|
|
|
|
|
"http://yourdomain.com/admin/f1cc3972a4b933c734b37906940cf69886161492ee4eb7c1faff5d7b5e92efb8"
|
|
|
|
|
|
|
|
Adapt "http" and "yourdomain.com" to your configuration.
|
|
|
|
|
|
|
|
In debug mode, the dev server will print the url when starting.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
2020-08-13 15:36:42 +03:00
|
|
|
ensure_app_context()
|
|
|
|
print(f"Zerobin version: {zerobin.__version__}")
|
|
|
|
print(f"Admin URL (to moderate pastes): {settings.ADMIN_URL}")
|
|
|
|
print(f"Data dir (pastes and counter): {settings.DATA_DIR}")
|
|
|
|
print(
|
|
|
|
f"Config dir (config file, secret key, admin password and custom views): {settings.CONFIG_DIR}"
|
|
|
|
)
|
|
|
|
print(
|
|
|
|
f"Static files dir (to configure apache, nging, etc.): {settings.STATIC_FILES_ROOT}"
|
|
|
|
)
|
2020-08-12 10:19:38 +03:00
|
|
|
|
|
|
|
|
|
|
|
def set_admin_password(password):
|
|
|
|
""" Set the password for the admin
|
|
|
|
|
|
|
|
It will be stored as a scrypt hash in a file in the var dir.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
2020-08-13 15:36:42 +03:00
|
|
|
ensure_app_context()
|
2020-08-12 10:19:38 +03:00
|
|
|
settings.ADMIN_PASSWORD_FILE.write_bytes(hash_password(password))
|
|
|
|
|
|
|
|
|
2020-08-15 15:45:52 +03:00
|
|
|
def clean_expired_pastes(
|
|
|
|
*, dry_run=False, verbose=False, config_dir="", data_dir="",
|
|
|
|
):
|
2020-08-15 15:40:26 +03:00
|
|
|
""" Clean expired file pastes and empty paste directories
|
|
|
|
|
|
|
|
This features delete files from the data dir, make sure it's safe.
|
|
|
|
|
|
|
|
Use "dry_run" and "verbose" options to check first
|
|
|
|
"""
|
|
|
|
|
2020-08-15 15:45:52 +03:00
|
|
|
ensure_app_context(config_dir=config_dir, data_dir=data_dir)
|
2020-08-15 15:40:26 +03:00
|
|
|
|
|
|
|
print("Deleting expired pastes...")
|
|
|
|
i = 0
|
|
|
|
for p in Paste.iter_all():
|
|
|
|
if p.has_expired:
|
|
|
|
if not dry_run:
|
|
|
|
p.delete()
|
|
|
|
if verbose:
|
|
|
|
print(p.path, "has expired")
|
|
|
|
i += 1
|
|
|
|
if dry_run:
|
|
|
|
print(f"{i} pastes would have been deleted")
|
|
|
|
else:
|
|
|
|
print(f"{i} pastes deleted")
|
|
|
|
|
|
|
|
print("Deleting empty paste directories...")
|
|
|
|
i = 0
|
|
|
|
for p in settings.DATA_DIR.rglob("*"):
|
|
|
|
try:
|
|
|
|
if p.is_dir() and not next(p.iterdir(), None):
|
|
|
|
if not dry_run:
|
|
|
|
p.rmdir()
|
|
|
|
if verbose:
|
|
|
|
print(p, "is empty")
|
|
|
|
i += 1
|
|
|
|
except OSError as e:
|
|
|
|
print(f'Error while processing "{p}: {e}')
|
|
|
|
if dry_run:
|
|
|
|
print(f"{i} directories would have been deleted")
|
|
|
|
else:
|
|
|
|
print(f"{i} directories deleted")
|
|
|
|
print("Done")
|
|
|
|
|
|
|
|
|
2015-09-18 18:36:14 +03:00
|
|
|
def main():
|
2020-08-15 15:40:26 +03:00
|
|
|
subcommands = [
|
|
|
|
runserver,
|
|
|
|
delete_paste,
|
|
|
|
infos,
|
|
|
|
set_admin_password,
|
|
|
|
clean_expired_pastes,
|
|
|
|
]
|
2020-08-10 11:48:47 +03:00
|
|
|
subcommand_names = [
|
|
|
|
clize.util.name_py2cli(name)
|
|
|
|
for name in clize.util.dict_from_names(subcommands).keys()
|
|
|
|
]
|
2015-09-18 18:36:14 +03:00
|
|
|
if len(sys.argv) < 2 or sys.argv[1] not in subcommand_names:
|
|
|
|
sys.argv.insert(1, subcommand_names[0])
|
2020-08-15 15:40:26 +03:00
|
|
|
clize.run(runserver, delete_paste, infos, set_admin_password, clean_expired_pastes)
|
2015-09-18 18:36:14 +03:00
|
|
|
|