#!/usr/bin/env python
# -*- coding: utf-8 -*-

# This file is part of the  X2Go Project - http://www.x2go.org
# Copyright (C) 2011-2013 by Oleksandr Shneyder <oleksandr.shneyder@obviously-nice.de>
# Copyright (C) 2011-2013 by Heinz-Markus Graesing <heinz-m.graesing@obviously-nice.de>
# Copyright (C) 2012-2013 by Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
#
# X2Go Session Broker is free software; you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# X2Go Session Broker 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, write to the
# Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.

import os
import sys
import setproctitle
import argparse
import logging
import asyncore
import socket
import getpass
import logging.config
import pam

from pwd import getpwnam
from grp import getgrnam

PROG_NAME = os.path.basename(sys.argv[0])
PROG_OPTIONS = sys.argv[1:]
setproctitle.setproctitle("%s %s" % (PROG_NAME, " ".join(PROG_OPTIONS)))

from x2gobroker import __VERSION__
from x2gobroker import __AUTHOR__


class AuthClient(asyncore.dispatcher_with_send):

    def __init__(self, sock):
        asyncore.dispatcher_with_send.__init__(self, sock)
        self._buf = ''

    def handle_read(self):
        data = self._buf + self.recv(1024)
        if not data:
            self.close()
            return
        reqs, data = data.rsplit('\n', 1)
        self._buf = data
        for req in reqs.split('\n'):
            try:
                user, passwd, service = req.split()
            except:
                self.send('bad\n')
            else:
                if pam.authenticate(user, passwd, service):
                    self.send('ok\n')
                else:
                    self.send('fail\n')

    def handle_close(self):
        self.close()


class AuthService(asyncore.dispatcher_with_send):

    def __init__(self, socketfile, owner='root', group_owner='root', permissions='0660'):
        asyncore.dispatcher_with_send.__init__(self)
        self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
        self.set_reuse_addr()
        self.bind(socketfile)
        os.chown(socketfile, getpwnam(owner).pw_uid, getgrnam(group_owner).gr_gid)
        os.chmod(socketfile, int(permissions, 8))
        self.listen(1)

    def handle_accept(self):
        conn, _ = self.accept()
        AuthClient(conn)


def loop():
    asyncore.loop()


# normally this would go into defaults.py, however, we do not want to pull in defaults.py here as that will create
# unwanted logfiles (access.log, broker.log, error.log) when x2gobroker-authservice is installed as standalone service
if os.environ.has_key('X2GOBROKER_DEBUG'):
    X2GOBROKER_DEBUG = ( os.environ['X2GOBROKER_DEBUG'].lower() in ('1', 'on', 'true', 'yes', ) )
else:
    X2GOBROKER_DEBUG = False
if os.environ.has_key('X2GOBROKER_TESTSUITE'):
    X2GOBROKER_TESTSUITE = ( os.environ['X2GOBROKER_TESTSUITE'].lower() in ('1', 'on', 'true', 'yes', ) )
else:
    X2GOBROKER_TESTSUITE = False
if os.environ.has_key('X2GOBROKER_DAEMON_USER'):
    X2GOBROKER_DAEMON_USER=os.environ['X2GOBROKER_DAEMON_USER']
else:
    X2GOBROKER_DAEMON_USER="x2gobroker"
if os.environ.has_key('X2GOBROKER_AUTHSERVICE_LOGCONFIG'):
    X2GOBROKER_AUTHSERVICE_LOGCONFIG=os.environ['X2GOBROKER_AUTHSERVICE_LOGCONFIG']
else:
    X2GOBROKER_AUTHSERVICE_LOGCONFIG="/etc/x2go/broker/x2gobroker-authservice-logger.conf"
if os.environ.has_key('X2GOBROKER_AUTHSERVICE_SOCKET'):
    X2GOBROKER_AUTHSERVICE_SOCKET=os.environ['X2GOBROKER_AUTHSERVICE_SOCKET']
else:
    X2GOBROKER_AUTHSERVICE_SOCKET="/run/x2gobroker/x2gobroker-authservice.socket"

# standalone daemon mode (x2gobroker-authservice as daemon) or interactive mode (called from the cmdline)?
if getpass.getuser() in (X2GOBROKER_DAEMON_USER, 'root'):

    # we run in standalone daemon mode, so let's use the system configuration for logging
    logging.config.fileConfig(X2GOBROKER_AUTHSERVICE_LOGCONFIG)

    # create authservice logger
    logger_authservice = logging.getLogger('authservice')

else:
    logger_root = logging.getLogger()
    stderr_handler = logging.StreamHandler(sys.stderr)
    stderr_handler.setFormatter(logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt=''))

    # all loggers stream to stderr...
    logger_root.addHandler(stderr_handler)

    logger_authservice = logging.getLogger('authservice')
    logger_authservice.addHandler(stderr_handler)
    logger_authservice.propagate = 0


# raise log level to DEBUG if requested...
if X2GOBROKER_DEBUG and not X2GOBROKER_TESTSUITE:
    logger_authservice.setLevel(logging.DEBUG)

logger_authservice.info('X2Go Session Broker ({version}), written by {author}'.format(version=__VERSION__, author=__AUTHOR__))
logger_authservice.info('Setting up the PAM authentication service\'s environment...')
logger_authservice.info('  X2GOBROKER_DEBUG: {value}'.format(value=X2GOBROKER_DEBUG))
logger_authservice.info('  X2GOBROKER_AUTHSERVICE_SOCKET: {value}'.format(value=X2GOBROKER_AUTHSERVICE_SOCKET))

# check effective UID the broker runs as and complain appropriately...
if os.geteuid() != 0:
    logger_authservice.warn('X2Go Session Broker\'s PAM authentication service should run with root privileges to guarantee proper access to all PAM modules.')

if __name__ == '__main__':

    common_options = [
        {'args':['-s','--socket-file'], 'default': X2GOBROKER_AUTHSERVICE_SOCKET, 'metavar': 'AUTHSOCKET', 'help': 'socket file for AuthService communication', },
        {'args':['-o','--owner'], 'default': 'root', 'help': 'owner of the AuthService socket file', },
        {'args':['-g','--group'], 'default': 'root', 'help': 'group ownership of the AuthService socket file', },
        {'args':['-p','--permissions'], 'default': '0660', 'help': 'set these file permissions for the AuthService socket file', },

    ]
    p = argparse.ArgumentParser(description='X2Go Session Broker (PAM Authentication Service)',\
                                formatter_class=argparse.RawDescriptionHelpFormatter, \
                                add_help=True, argument_default=None)
    p_common = p.add_argument_group('common parameters')

    for (p_group, opts) in ( (p_common, common_options), ):
        for opt in opts:
            args = opt['args']
            del opt['args']
            p_group.add_argument(*args, **opt)

    cmdline_args = p.parse_args()

    socket_file = cmdline_args.socket_file
    AuthService(socket_file, owner=cmdline_args.owner, group_owner=cmdline_args.group, permissions=cmdline_args.permissions)
    try:
        loop()
    except KeyboardInterrupt:
        pass
