#!/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 = '2.1'
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 sys
import time
import argparse

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("--name",
                        help="The name of the machine. The default is the "
                             "hostname")
    parser.add_argument("--clearslot",
                        help="If the slot is populated, overwrite it!",
                        action="store_true")
    parser.add_argument("--nosslcheck",
                        help="Do not check SSL certificates.",
                        action="store_true")
    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

    # Create the privacyideaclient instance
    client = privacyideaclient(args.admin, password, args.url,
                               no_ssl_check=args.nosslcheck)

    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 = {"hostname": name,
              "challenge": binascii.hexlify(lukspassword)}

    response = client.get("/machine/authitem/luks", params)
    result = response.data.get("result")

    if result.get("status"):
        machinetokens = result.get("value").get("luks")
        if len(machinetokens) == 0:
            # No token available
            raise Exception("No token found for the application on this machine!")
        else:
            print "Found %i Authentication items to put into LUKS." % len(
                machinetokens)
            print "We will loop over these items."
            print 40*"="
            for auth_item in machinetokens:
                luks_key = auth_item.get("response")
                if len(luks_key) < 40:
                    raise Exception("The returned response is to small! %r" % len(luks_key))
                server_slot = auth_item.get("slot")
                server_partition = auth_item.get("partition")
                if server_slot is None:
                    raise Exception("The server is missing a slot information "
                                    "for this token on this application. "
                                    "You need to set an 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 'partition'")

                print "Doing slot %s on partition %s." % (server_slot,
                                                          server_partition)
                print 40*"="
                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)
    else:
        # False status
        print result.get("error").get("message")



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