#!/usr/bin/python3
# pylint: disable=invalid-name  # https://github.com/PyCQA/pylint/issues/516

"""
Script to use as a 'command' in an authorized_key file to allow authorization to mini-buildd uploads via SSH.

Steps to install 'uploads via SSH' (uses 'mini-buildd-uploader' as user name).

As user 'root'::

  adduser --disabled-password mini-buildd-uploader
  # OPTIONAL: Allows 'auth log' with the fingerprint
  adduser mini-buildd-uploader adm

As mini-buildd-uploader::

  gpg --gen-key    # Be sure to only have one secret key in the keyring; we will use the first found.
  mkdir -m700 .ssh
  edit ~/.dput.cf    # Put the dput.cf target of your mini-buildd instance here; First target found will be used.

As admin user at the mini-buildd instance (web app)::

  Generate a django pseudo user "ssh-uploads".
  In the new user's "Uploader" profile:
    Add the mini-buildd-uploader's GPG public key to the django users "Uploader" profile.
    Add the repos to access ("may upload to").
    PCA (prepare, check, activate) the new uploader profile.

To authorize a SSH Key, as user mini-buildd-uploader, add a line like this::

  command="/usr/share/mini-buildd/bin/mbd-ssh-uploader-command" ssh-rsa AA...

per ssh user key.

As SSH uploader::

  Run 'ssh mini-buildd-uploader@the.mini-buildd.host'. This
  (will fail but) gives you a hint how to configure your
  '.dput.cf'
  Patch up your .dput.cf, then you should be able to upload like
  normal via dput with the new target.
"""

import sys
import os
import glob
import shutil
import socket
import tempfile
import subprocess
import re
import getpass
import syslog


# Use syslog, facility user, to log access and key
syslog.openlog(facility=syslog.LOG_USER)


def auth_log(msg):
    """
    Uff. Dirty hack to get an 'auth' log including the ssh fingerprint.

    Needs sshd on loglevel=VERBOSE, and the user needs access to
    auth.log (i.e., add user to group 'adm' in standard Debian).
    """
    def getpppid():
        """Get grandparent PID."""
        return subprocess.check_output("/bin/ps -p {ppid} -oppid=".format(ppid=os.getppid()), shell=True).strip()

    def get_last_matching_line(file_name, regex):
        result = None
        with open(file_name) as f:
            for line in f:
                if re.match(regex, line):
                    result = line
        return result

    def get_fingerprint():
        try:
            line = get_last_matching_line("/var/log/auth.log", r"^.*sshd\[{pppid}\]: Found matching .* key: .*".format(pppid=getpppid()))
            fp = line.rpartition(" ")[2].strip()
        except BaseException:
            fp = "NO_FINGERPRINT_FOUND"
        return fp

    command = os.environ.get("SSH_ORIGINAL_COMMAND", "NO_COMMAND_FOUND")
    ip = os.environ.get("SSH_CONNECTION", "NO_IP_FOUND").partition(" ")[0]
    try:
        host = socket.gethostbyaddr(ip)[0]
    except BaseException:
        host = ip

    syslog.syslog(syslog.LOG_NOTICE, "{msg}: [{user}] {fp}@{host} \"{command}\"".format(msg=msg,
                                                                                        user=getpass.getuser(),
                                                                                        host=host,
                                                                                        fp=get_fingerprint(),
                                                                                        command=command))


def log(*args):
    print(*args, file=sys.stderr)


def get_key_id():
    return subprocess.check_output("gpg --list-secret-keys --with-colons | grep --max-count=1 '^sec' | cut -d: -f5", shell=True).strip()


def get_dput_target():
    return subprocess.check_output(r"grep --max-count=1 '^\[.*\]' ~/.dput.cf", shell=True).strip("\n[]")


RETVAL = 0
try:
    # Prepare incoming dir and tmp dir for this upload
    INCOMING = os.path.expanduser("~") + "/incoming/"
    try:
        os.makedirs(INCOMING)
    except BaseException:
        pass
    TMPDIR = tempfile.mkdtemp(dir=INCOMING)
    log("I: Accepting incoming dir: {i}".format(i=INCOMING))
    log("I: Using upload tmp dir: {t}".format(t=TMPDIR))

    # Build up secure command to use from original command
    ORIGINAL_COMMAND = os.environ.get("SSH_ORIGINAL_COMMAND", "").split()
    log("I: Original command: ", ORIGINAL_COMMAND)
    COMMAND = []
    ALLOWED_ITEMS = ["scp", "-p", "-d", "-t"]
    IT = iter(ORIGINAL_COMMAND)
    for N in IT:
        if N in ALLOWED_ITEMS:
            if N == "-t":
                target = next(IT)
                # Workaround: squeeze's dput seems to add a "--" between -t and the directory option, we should skip that
                if target == "--":
                    target = next(IT)
                if target != INCOMING:
                    raise Exception("Seems you got the incoming dir wrong.")
                COMMAND.append("-t")
                COMMAND.append(TMPDIR)
            else:
                COMMAND.append(N)
        else:
            raise Exception("Option not allowed: {o}.".format(o=N))

    # Transfer files
    log("I: Uploading files via: ", COMMAND)
    subprocess.check_call(COMMAND)
    log("I: Upload successful to: {t}".format(t=TMPDIR))

    # Compute changes file
    CHANGES = glob.glob(TMPDIR + "/*.changes")
    log("I: Found changes: {c}".format(c=CHANGES))
    if len(CHANGES) != 1:
        raise Exception("{} changes files uploaded (only upload exactly one).".format(len(CHANGES)))

    # Re-sign changes file with our GPG key
    SIGN_COMMAND = "debsign --re-sign -k{k} {c}".format(k=get_key_id(), c=CHANGES[0])
    log("I: SIGN_COMMAND: {c}".format(c=SIGN_COMMAND))
    log(subprocess.check_output(SIGN_COMMAND, shell=True))

    # Upload to the actual mini-buildd
    DPUT_COMMAND = "dput {t} {c}".format(t=get_dput_target(), c=CHANGES[0])
    log("I: DPUT_COMMAND: {c}".format(c=DPUT_COMMAND))
    log(subprocess.check_output(DPUT_COMMAND, shell=True))

    # Try to do the auth log
    auth_log("SUCCESS")

except BaseException as e:
    auth_log("FAILED")

    log("""\

*ERROR*: {e}

Please only use 'dput' on me, and check that your target in
'~/.dput.cf' looks like this:
---
[{t}]
method   = scp
login    = {u}
fqdn     = {h}
incoming = {i}
---
    """.format(e=e, t=get_dput_target() + "-ssh-upload", u=os.getenv("USER"), h=socket.getfqdn(), i=INCOMING))
    RETVAL = 1
finally:
    shutil.rmtree(TMPDIR, ignore_errors=True)

sys.exit(RETVAL)
