#!/usr/bin/python
# -*- coding: utf-8 -*-
#
#  privacyIDEA
#  (c) 2014 Cornelius Kölbel, cornelius@privacyidea.org
#
# This code is free software; you can redistribute it and/or
# modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
# License as published by the Free Software Foundation; either
# version 3 of the License, or any later version.
#
# This code is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU AFFERO GENERAL PUBLIC LICENSE for more details.
#
# You should have received a copy of the GNU Affero General Public
# License along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
'''
This tool is used to fill the LUKS keyslot with the response
generated by a yubikey managed by the privacyIDEA server
'''

VERSION = '1.3'
DESCRIPTION=__doc__
DEBUG = False

import subprocess
from getopt import getopt, GetoptError
import getpass
from privacyideautils.clientutils import *
import binascii
import socket
from tempfile import NamedTemporaryFile
import os, stat
import time
import argparse

SLOT_OPTION_KEY = "option_slot"
PARTITION_OPTION_KEY = "option_partition"
DELAY = 2


def set_slot(key=None,
             slot=None,
             partition=None,
             clear_slot=False):
    '''
    Set the "key" on the LUKS slot "slot"
    
    touch $TMP_FILE
    chmod 600 $TMP_FILE
    echo -n "key" > $TMP_FILE
    cryptsetup --key-slot=$SLOT luksAddKey $DISK $TMP_FILE
    rm $TMP_FILE
    '''
    # clear the slot
    if clear_slot:
        print("\nClear the slot %i. You will need to provide an "
              "existing LUKS password!" % slot)
        time.sleep(DELAY)
        p = subprocess.Popen(["cryptsetup",
                              "luksKillSlot",
                              partition,
                              "%i" % slot],
                             cwd=None,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE,
                             shell=False)
        (result, error) = p.communicate()
        rcode = p.returncode
        print result, error
        if rcode != 0:
            print("Error during clearing the slot %i. "
                  "Did you provide the right password? (%i)" % (slot, rcode))
            sys.exit(4)


    print("\nWe are about to set a new LUKS password in slot %i "
          "on partition %r." % (slot, partition))
    print "You will be asked for an existing slot password!"
    time.sleep(DELAY)
        
    f = NamedTemporaryFile(delete=False)
    filename = f.name
    f.write(key)
    f.close()
    
    # create the slot
    p = subprocess.Popen(["cryptsetup",
                          "--key-slot=%i" % slot,
                          "luksAddKey",
                          partition,
                          filename],
                         cwd=None,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE,
                         shell=False)
    (result, error) = p.communicate()
    rcode = p.returncode
    print result, error   
    # overwrite and clean up the mess
    f = open(filename, "w")
    f.write("X" * 40)
    f.close()
    os.unlink(filename)
    if rcode != 0:
        print("Error during writing the new slot %i. "
              "Did you provide the right password? (%i)" % (slot, rcode))
        sys.exit(5)


def create_arguments():
    parser = argparse.ArgumentParser(description=DESCRIPTION,
                                     fromfile_prefix_chars='@')
    parser.add_argument("-U", "--url",
                        help="The URL of the privacyIDEA server including "
                        "protocol and port like "
                        "https://localhost:5001",
                        required=True)
    parser.add_argument("-a", "--admin",
                        help="The name of the administrator like "
                        "admin@admin or admin",
                        required=True)
    parser.add_argument("-r", "--adminrealm",
                        help="The realm of the administrator like "
                        "'admin'",
                        default="")
    parser.add_argument("-p", "--password",
                        help="The password of the administrator. Please "
                        "avoid to post the password at the command line. "
                        "You will be asked for it - or you can provide the "
                        "password in a configuration file. "
                        "Note, that you can write a file password.txt "
                        "containing two lines '--password' and the second "
                        "line the password itself and add this to the "
                        "command line with @password.txt")
    parser.add_argument("-v", "--version",
                        help="Print the version of the program.",
                        action='version', version='%(prog)s ' + VERSION)
    parser.add_argument("--lukspassword",
                        help="The password for the LUKS usage")
    parser.add_argument("--luksslot",
                        help="If the server has no information on a LUKS "
                        "we can set the slot here.")
    parser.add_argument("--name",
                        help="The name of the machine")
    parser.add_argument("--clearslot",
                        help="The password for the LUKS usage",
                        action="store_true")
    parser.add_argument("--serial",
                        help="The serial numer of the token, if only one "
                        "token is to be retrieved.")
    args = parser.parse_args()
    return args

##### main

def main():
    args = create_arguments()

    if not args.password:
        password = getpass.getpass(prompt="Please enter password for"
                                   " '%s':" % args.admin)
    else:
        password = args.password

    protocol = args.url.split(":", 1)[0]
    host = args.url.split(":", 1)[1].strip("/")
    # Create the privacyideaclient instance
    client = privacyideaclient(protocol,
                               host,
                               admin=args.admin,
                               adminpw=password,
                               adminrealm=args.adminrealm)
    
    if args.lukspassword is None:
        lukspassword = getpass.getpass("Yubikey password for LUKS slot:")
    else:
        lukspassword = args.lukspassword
        
    if args.name is None:
        name = socket.gethostname()
    else:
        name = args.name

    params = {"name": name,
              "application": "luks",
              "challenge": binascii.hexlify(lukspassword)}
    if args.serial:
        params["serial"] = args.serial
    ret = client.connect("/machine/gettokenapps", {}, params)
    result = ret.get("result")
    total = result.get("value").get("total")
    
    if total > 1:
        # More than one token found!
        machines = result.get("value").get("machines")
        print "Available serial numbers:"
        print "-------------------------" 
        for _machine_id, machine in machines.items():
            print machine.get("serial")
        raise Exception("Several tokens are assigned to the application"
                        "on this client. You need to specify one token"
                        "with the --serial parameter")
    elif total == 0:
        # No token available
        raise Exception("No token found for the application on this machine!")
    else:
        # exactly one token
        machines = result.get("value").get("machines")
        machine_values = machines.get(machines.keys()[0])
        auth_item = machine_values.get("auth_item")
        luks_key = auth_item.get("response")
        if len(luks_key) < 40:
            raise Exception("The returned response is to small! %r" % len(luks_key))
        options = machine_values.get("options")
        server_slot = options.get(SLOT_OPTION_KEY, args.luksslot)
        server_partition = options.get(PARTITION_OPTION_KEY)
        if server_slot is None:
            if slot:
                # TODO: Now we need to set the slot on the server
                pass
            else:
                raise Exception("The server is missing a slot information "
                                "for this token on this application. "
                                "You need to set an option 'option_slot'")
        
        if server_partition is None:
            raise Exception("The server is missing a partition information "
                            "for this token on this application. "
                            "You need to set an option 'option_partition'")
        '''
        A server slot exists
        Now we can use
        - server_slot
        - luks_key 
        to fill the LUKS slot
        '''
        server_slot = int(server_slot)
        if server_slot == 0:
            raise Exception("We will not overwrite the LUKS slot 0! "
                            "set another slot on the server!")
        if server_slot > 7:
            raise Exception("The maximum slot number is 7! "
                            "correct the 'option_slot' on the server "
                            "side.")
        set_slot(key=luks_key,
                 slot=server_slot,
                 partition=server_partition,
                 clear_slot=args.clearslot)



if __name__ == '__main__':
    if DEBUG:
        main()
    else:
        try:
            main()
        except Exception as ex:
            print "Error: %s" % ex
            sys.exit(5)
