#!/usr/bin/python3
# -*- coding: utf-8 -*-

# Copyright (C) 2012-2014 Stéphane Graber
# Author: Stéphane Graber <stgraber@ubuntu.com>

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You can find the license on Debian systems in the file
# /usr/share/common-licenses/GPL-2

import argparse
import configparser
import gettext
import os
import socket
import subprocess
import sys

_ = gettext.gettext
gettext.textdomain("edubuntu-server-auth")

# Constants
PKG_LIST = ("libpam-sss", "libnss-sss", "libsss-sudo",
            "libsasl2-modules-gssapi-heimdal", "samba-common-bin")


# Common functions
def backup_config(path):
    if os.path.exists(path) and not os.path.exists("%s.pre-edubuntu" % path):
        os.rename(path, "%s.pre-edubuntu" % path)


def get_username(args):
    username = ""
    password = ""

    if hasattr(args, "username"):
        username = args.username
    else:
        username = os.environ['USER']

    if hasattr(args, "password"):
        if args.password == "-":
            password = sys.stdin.read().strip()
        else:
            password = args.password

    if password:
        return "%s%%%s" % (username, password)
    else:
        return username


def restore_config(path):
    if os.path.exists(path):
        os.remove(path)

    if os.path.exists("%s.pre-edubuntu" % path):
        os.rename("%s.pre-edubuntu" % path, path)


# The various actions
def join_domain(args):
    krb5_domain = args.domain.upper()
    ldap_dn = "DC=%s" % ",DC=".join(args.domain.split("."))
    smb_workgroup = args.domain.split(".")[0].upper()
    smb_hostname = socket.gethostname().upper()
    domain_sid = None
    enctypes = "rc4-hmac aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96"

    # Mark the packages for installation
    cache = apt.Cache()
    for pkg in PKG_LIST:
        if pkg in cache:
            cache[pkg].mark_install()
        else:
            print("Unable to find package: %s" % pkg)
            sys.exit(1)

    if cache.get_changes():
        print("Installing %s needed packages." % len(cache.get_changes()))
        cache.update()
        cache.commit()

    # Configure kerberos
    backup_config("/etc/krb5.conf")
    backup_config("/etc/krb5.keytab")
    with open("/etc/krb5.conf", "w+") as fd:
        fd.write("""[libdefaults]
    default_realm = %s
    dns_lookup_realm = false
    dns_lookup_kdc = true
    ticket_lifetime = 24h
    allow_weak_crypto = true
    default_tgs_enctypes = %s
    default_tkt_enctypes = %s
    permitted_enctypes = %s


[domain_realm]
    .%s = %s
    %s = %s
""" % (
            krb5_domain, enctypes, enctypes, enctypes,
            args.domain, krb5_domain, args.domain, krb5_domain))
        os.fchmod(fd.fileno(), 0o644)
        os.fchown(fd.fileno(), 0, 0)

    # Configure samba
    backup_config("/etc/samba/smb.conf")
    with open("/etc/samba/smb.conf", "w+") as fd:
        fd.write("""[global]
workgroup = %s
realm = %s
server string = %s
netbios name = %s
security = ads
kerberos method = system keytab
passdb backend = tdbsam
""" % (smb_workgroup, args.domain, smb_hostname, smb_hostname))
        os.fchmod(fd.fileno(), 0o644)
        os.fchown(fd.fileno(), 0, 0)

    command = ["net", "ads", "join", "-U", get_username(args)]

    while 1:
        # Join
        subprocess.call(command)

        # Get the SID
        net = subprocess.Popen(['net', 'getdomainsid'], stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE, universal_newlines=True)
        net.wait()
        for line in net.stdout:
            line = line.strip()
            if line.startswith("SID for domain"):
                domain_sid = line.split()[-1]
                break

        if not domain_sid:
            print("Failed to join domain. Try again.")
            continue

        break

    # Configure lightdm
    if os.path.exists("/etc/lightdm/lightdm.conf"):
        write_lightdm = False

        lightdm = configparser.ConfigParser()
        lightdm.read("/etc/lightdm/lightdm.conf")
        if not lightdm.has_section("SeatDefaults"):
            lightdm.add_section("SeatDefaults")

        if not lightdm.has_option("SeatDefaults", "greeter-show-manual-login"):
            lightdm.set("SeatDefaults", "greeter-show-manual-login", "true")
            write_lightdm = True
        elif lightdm.get("SeatDefaults",
                         "greeter-show-manual-login") != "true":
            lightdm.set("SeatDefaults", "greeter-show-manual-login", "true")
            write_lightdm = True

        if write_lightdm:
            with open("/etc/lightdm/lightdm.conf", "w+") as fd:
                lightdm.write(fd)

    # Configure PAM
    with open("/usr/share/pam-configs/edubuntu-domain", "w+") as fd:
        fd.write("""Name: edubuntu-server settings
Default: yes
Priority: 128

Session-Type: Additional
Session-Interactive-Only: yes
Session:
    optional                    pam_mkhomedir.so
""")
        os.fchmod(fd.fileno(), 0o644)
        os.fchown(fd.fileno(), 0, 0)

    with open(os.devnull, "w") as fd:
        subprocess.call(["pam-auth-update", "--package"],
                        stdout=fd, stderr=fd)

    # Configure sssd
    with open(os.devnull, "w") as fd:
        subprocess.call(["stop", "sssd"],
                        stdout=fd, stderr=fd)

    backup_config("/etc/sssd/sssd.conf")
    with open("/etc/sssd/sssd.conf", "w+") as fd:
        fd.write("""[sssd]
domains = Edubuntu
services = nss, pam, sudo
config_file_version = 2
sbus_timeout = 30
debug_level = 0

[nss]
default_shell = /bin/bash

[domain/Edubuntu]
enumerate = false
cache_credentials = true
fallback_homedir = /home/%%u

id_provider = ldap
auth_provider = krb5
chpass_provider = krb5
sudo_provider = ldap

dns_discovery_domain = %s
krb5_realm = %s

ldap_idmap_default_domain_sid = %s
ldap_schema = ad
ldap_id_mapping = true
ldap_user_gecos = displayName
ldap_force_upper_case_realm = true

ldap_sudo_search_base = OU=sudoers,%s

ldap_sasl_mech = GSSAPI
""" % (args.domain, krb5_domain, domain_sid, ldap_dn))
        os.fchmod(fd.fileno(), 0o600)
        os.fchown(fd.fileno(), 0, 0)

    with open(os.devnull, "w") as fd:
        subprocess.call(["start", "sssd"],
                        stdout=fd, stderr=fd)


def leave_domain(args):
    # Unconfigure samba
    if os.path.exists("/etc/samba/smb.conf"):
        command = ["net", "ads", "leave", "-U", get_username(args)]

        if subprocess.call(command) != 0:
            print("Failed to leave the domain.")
            sys.exit(1)
    restore_config("/etc/samba/smb.conf")

    # Unconfigure sssd
    restore_config("/etc/sssd/sssd.conf")

    with open(os.devnull, "w") as fd:
        if os.path.exists("/etc/sssd/sssd.conf"):
            subprocess.call(["initctl", "restart", "sssd"],
                            stdout=fd, stderr=fd)
        else:
            subprocess.call(["initctl", "stop", "sssd"],
                            stdout=fd, stderr=fd)

    # Remove the Kerberos configuration
    restore_config("/etc/krb5.conf")
    restore_config("/etc/krb5.keytab")

    # Remove the PAM configuration
    restore_config("/usr/share/pam-configs/edubuntu-domain")
    with open(os.devnull, "w") as fd:
        subprocess.call(["pam-auth-update", "--package", "--remove",
                         "edubuntu-domain"],
                        stdout=fd, stderr=fd)

    # Unconfigure lightdm
    if os.path.exists("/etc/lightdm/lightdm.conf"):
        lightdm = configparser.ConfigParser()
        lightdm.read("/etc/lightdm/lightdm.conf")

        if lightdm.has_section("SeatDefaults") and \
                lightdm.has_option("SeatDefaults",
                                   "greeter-show-manual-login"):
            lightdm.remove_option("SeatDefaults", "greeter-show-manual-login")

            with open("/etc/lightdm/lightdm.conf", "w+") as fd:
                lightdm.write(fd)


# Begin parsing the command line
parser = argparse.ArgumentParser(
    description=_("Edubuntu server authentication"),
    formatter_class=argparse.RawTextHelpFormatter)

# Commands
subparsers = parser.add_subparsers()
subparser_join = subparsers.add_parser('join', help=_('Join the domain'))
subparser_join.set_defaults(action=join_domain)

subparser_join.add_argument(dest="domain", metavar="DOMAIN-FQDN",
                            help=_("Fully-Qualified-Domain-Name for the domain"
                                   "(e.g. edubuntu.org)"))

subparser_join.add_argument("-U", "--username", dest="username",
                            metavar="USERNAME",
                            help=_("Username of a Domain Administrator"))
subparser_join.add_argument("-P", "--password", dest="password",
                            metavar="PASSWORD",
                            help=_("Password of a Domain Administrator "
                                   "('-' for stdin)"))

subparser_leave = subparsers.add_parser('leave', help=_('Leave the domain'))
subparser_leave.add_argument("-U", "--username", dest="username",
                             metavar="USERNAME",
                             help=_("Username of a Domain Administrator"))
subparser_leave.add_argument("-P", "--password", dest="password",
                             metavar="PASSWORD",
                             help=_("Password of a Domain Administrator "
                                    "('-' for stdin)"))

subparser_leave.set_defaults(action=leave_domain)


args = parser.parse_args()

# Basic requirements check
## Check that we have an action
if not hasattr(args, "action"):
    parser.error(_("You must specifiy an action."))
    sys.exit(1)

## The user needs to be uid 0
if not os.geteuid() == 0:
    parser.error(_("You must be root to run this script. "
                   "Try running: sudo %s" % (sys.argv[0])))
    sys.exit(1)

# Import apt now so we don't have to build-depend on it
import apt

args.action(args)
