Skip to content

Instantly share code, notes, and snippets.

@vsajip
Created June 13, 2025 23:09
Show Gist options
  • Save vsajip/e495302f2f3c9eacb4e3ca2f01317819 to your computer and use it in GitHub Desktop.
Save vsajip/e495302f2f3c9eacb4e3ca2f01317819 to your computer and use it in GitHub Desktop.
Build a version of the SQLite3 Multi Cipher shell with readline support.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2025 Red Dove Consultants Limited. License: 3-Clause BSD.
#
import argparse
import logging
import os
import subprocess
import sys
import tarfile
from urllib.request import urlretrieve
import zipfile
DEBUGGING = 'PY_DEBUG' in os.environ
logger = logging.getLogger(__name__)
SQLITE_DOWNLOAD_VERSION = '3500100'
SQLITE_DOWNLOAD_URL = f'https://sqlite.org/2025/sqlite-autoconf-{SQLITE_DOWNLOAD_VERSION}.tar.gz'
MC_VERSION = '2.1.3'
MC_SQLITE_VERSION = '3.50.1'
MC_DOWNLOAD_URL = ('https://github.com/utelle/SQLite3MultipleCiphers/releases/download/'
f'v{MC_VERSION}/sqlite3mc-{MC_VERSION}-sqlite-{MC_SQLITE_VERSION}-amalgamation.zip')
def extract_sqlite(tar_path, dest_dir):
try:
with tarfile.open(tar_path, 'r:gz', errorlevel=1) as tf:
tf.extractall(dest_dir)
members = tf.getmembers()
member = members[0]
os.rename(member.name, 'sqlite')
except tarfile.ReadError as e:
logger.exception('Error reading archive: %s', e)
raise
except OSError as e:
logger.exception('Error writing files: %s', e)
raise
def process(options):
# Get the relevant SQLite3 archive
if not os.path.exists('sqlite.tar.gz'):
urlretrieve(SQLITE_DOWNLOAD_URL, 'sqlite.tar.gz')
# Get the relevant Multi Ciphers archive
if not os.path.exists('mc.zip'):
urlretrieve(MC_DOWNLOAD_URL, 'mc.zip')
# Extract the SQLite archive
if not os.path.isdir('sqlite'):
extract_sqlite('sqlite.tar.gz', os.getcwd())
# Overwrite some SQLite3 files with the Multi Cipher versions
with zipfile.ZipFile('mc.zip') as zf:
with zf.open('sqlite3mc_amalgamation.h') as f:
data = f.read()
with open('sqlite/sqlite3.h', 'wb') as f:
f.write(data)
with zf.open('sqlite3mc_amalgamation.c') as f:
data = f.read()
with open('sqlite/sqlite3.c', 'wb') as f:
f.write(data)
with zf.open('shell.c') as f:
data = f.read()
data = data.replace(b'extern char* sqlite3mc_version();',
b'extern const char* sqlite3mc_version();', 2)
with open('sqlite/shell.c', 'wb') as f:
f.write(data)
url = f'https://raw.githubusercontent.com/utelle/SQLite3MultipleCiphers/refs/tags/v{MC_VERSION}/src/zlibwrap.h'
urlretrieve(url, 'sqlite/zlibwrap.h')
subprocess.check_call(['sh', 'configure'], cwd='sqlite')
subprocess.check_call(['make'], cwd='sqlite')
def main():
fn = os.path.basename(__file__)
fn = os.path.splitext(fn)[0]
lfn = os.path.expanduser('~/logs/%s.log' % fn)
if os.path.isdir(os.path.dirname(lfn)):
logging.basicConfig(level=logging.DEBUG,
filename=lfn,
filemode='w',
format='%(message)s')
adhf = argparse.ArgumentDefaultsHelpFormatter
ap = argparse.ArgumentParser(formatter_class=adhf, prog=fn)
# aa = ap.add_argument
# aa('input', metavar='INPUT', help='File to process')
# aa('--flag', '-f', default=False, action='store_true', help='Boolean option')
options = ap.parse_args()
process(options)
if __name__ == '__main__':
try:
rc = main()
except KeyboardInterrupt:
rc = 2
except Exception as e:
if DEBUGGING:
s = ' %s:' % type(e).__name__
else:
s = ''
sys.stderr.write('Failed:%s %s\n' % (s, e))
if DEBUGGING:
import traceback
traceback.print_exc()
rc = 1
sys.exit(rc)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment