#!/usr/bin/python
# 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 Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# Copyright 2005 Dan Williams <dcbw@redhat.com> and Red Hat, Inc.


import sys
import os
import socket
import signal
import time
from plague import AuthedXMLRPCServer
from plague import HTTPServer
from plague import daemonize
from plague import DebugUtils
import SimpleXMLRPCServer
from optparse import OptionParser
import threading

sys.path.append('/usr/share/plague/server')

import User
import BuildMaster
import BuilderManager
import DBManager
import Config
from UserInterface import UserInterfaceSSLAuth
from UserInterface import UserInterfaceNoAuth


class AuthenticatedSSLXMLRPCServer(AuthedXMLRPCServer.AuthedSSLXMLRPCServer):
    """
    SSL XMLRPC server that authenticates clients based on their certificate.
    """

    def __init__(self, address, certs, cfg):
        AuthedXMLRPCServer.AuthedSSLXMLRPCServer.__init__(self, address, self.auth_cb, certs)
        self.authenticator = User.Authenticator(cfg)

    def auth_cb(self, request, client_address):
        peer_cert = request.get_peer_certificate()
        email = peer_cert.get_subject().emailAddress
        try:
            user = self.authenticator.new_authed_user(email, client_address)
        except Exception:
            user = None
        return user


#################################################################

bm_server = None
bm_server_addr = None

def exit_handler(signum, frame):
    global bm_server, bm_server_addr
    print "Received SIGTERM, quitting..."
    bm_server.stop()

    (host,port) = bm_server_addr
    print "Sending fake request to %s:%s to trigger shutdown..." % (host, port)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((host, port))
    s.send('WANNAQUIT')
    s.close()

def main():
    global bm_server, bm_server_addr

    usage = "Usage: %s [-p <pidfile>] [-l <logfile>] [-d] [-c <configfile>]" % sys.argv[0]
    parser = OptionParser(usage=usage)
    parser.add_option("-p", "--pidfile", default=None,
        help='file to write the PID to')
    parser.add_option("-l", "--logfile", default=None,
        help="location of file to write log output to")
    parser.add_option("-d", "--daemon", default=False, action="store_true",
        help="daemonize (i.e., detach from the terminal)")
    parser.add_option("-c", "--configfile", default="/etc/plague/server/plague-server.cfg",
        help='server configuration file, default: /etc/plague/server/plague-server.cfg')
    (opts, args) = parser.parse_args()

    if not opts.configfile:
        print "Must specify a config file."
        sys.exit(1)

    if opts.pidfile and os.path.exists(opts.pidfile):
        os.unlink(opts.pidfile)

    if opts.daemon:
        ret=daemonize.createDaemon(opts.pidfile)
        if ret:
            print "Daemonizing failed!"
            sys.exit(2)

    if opts.logfile:
        # 1 == line buffer the log file
        log=open(opts.logfile, 'a', 1)
        sys.stdout=log
        sys.stderr=log

    # Load in our config, filling in with defaults if it doesn't exist
    cfg = Config.ServerConfig(opts.configfile)
    cfg.load_target_configs()
    if len(cfg.targets()) == 0:
        print "You need at least one target to do anything useful."
        sys.exit(3)

    hostname = cfg.get_str("General", "hostname")

    # Start the debugging server
    use_tbs = False
    try:
        use_tbs = cfg.get_bool("General", "traceback_server")
        if use_tbs:
            tbs_address = "/tmp/plague-server-debug-%s" % hostname
            tbs = DebugUtils.ThreadTracebackServer(tbs_address)
            tbs.start()
    except Config.BaseConfig.ConfigError:
        pass

    dbm = DBManager.DBManager(cfg)

    builder_manager = BuilderManager.BuilderManager(cfg)

    # Create the BuildMaster thread
    bm = BuildMaster.BuildMaster(builder_manager, dbm, cfg)
    bm.start()

    # Create the BuildMaster XMLRPC server
    port = cfg.get_int("UI", "port")
    ui = None
    try:
        if cfg.get_bool("UI", "use_ssl") == True:
            ui_certs = {}
            ui_certs['key_and_cert'] = cfg.get_str("SSL", "server_key_and_cert")
            ui_certs['ca_cert'] = cfg.get_str("SSL", "ca_cert")
            ui_certs['peer_ca_cert'] = cfg.get_str("UI", "client_ca_cert")
            ui = UserInterfaceSSLAuth(builder_manager, bm, dbm, cfg)
            bm_server = AuthenticatedSSLXMLRPCServer((hostname, port), ui_certs, cfg)
        else:
            ui = UserInterfaceNoAuth(builder_manager, bm, dbm, cfg)
            bm_server = AuthedXMLRPCServer.AuthedXMLRPCServer((hostname, port))
        bm_server_addr = (hostname,port)
    except socket.error, e:
        if e[0] == 98:      # Address already in use
            print "Error: couldn't bind to address '%s:%s'.  Is the server already running?" % (hostname, port)
            os._exit(1)

    bm_server.register_instance(ui)

    # SRPM fileserver
    SRPM_SERVER_PORT = 8886
    http_dir = os.path.join(cfg.get_str("Directories", "server_work_dir"), "srpm_http_dir")
    srpm_server_certs = {}
    if cfg.get_bool("Builders", "use_ssl"):
        srpm_server_certs['key_and_cert'] = cfg.get_str("SSL", "server_key_and_cert")
        srpm_server_certs['ca_cert'] = cfg.get_str("SSL", "ca_cert")
        srpm_server_certs['peer_ca_cert'] = cfg.get_str("SSL", "ca_cert")
    srpm_server = HTTPServer.PlgHTTPServerManager((hostname, SRPM_SERVER_PORT), http_dir, srpm_server_certs)
    srpm_server.start()

    # Create dummy thread just to register main thread's name
    dummy = threading.Thread()
    dummy.setName("MainThread")
    DebugUtils.registerThreadName(dummy)
    del dummy

    # Set up our termination handler
    signal.signal(signal.SIGTERM, exit_handler)

    print "Build Server accepting requests on %s:%d.\n" % (hostname, port)

    # Serve requests until we're told to stop
    try:
        bm_server.serve_forever()
    except KeyboardInterrupt:
        bm_server.server_close()

    # Make sure the BuildMaster thread shuts down
    print "Shutting down..."
    bm.stop()
    srpm_server.stop()
    if use_tbs:
        tbs.stop()

    print "Done."
    os._exit(0)


if __name__ == '__main__':
    main()
