#
# ovirt-engine-setup -- ovirt engine setup
# Copyright (C) 2014 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#


"""websocket proxy plugin."""


import contextlib
import os
import urllib2
import tempfile


import gettext
_ = lambda m: gettext.dgettext(message=m, domain='ovirt-engine-setup')


from M2Crypto import X509
from M2Crypto import EVP
from M2Crypto import RSA


from otopi import constants as otopicons
from otopi import filetransaction
from otopi import util
from otopi import plugin


from ovirt_engine_setup import constants as osetupcons
from ovirt_engine_setup.websocket_proxy import constants as owspcons
from ovirt_engine_setup import dialog


@util.export
class Plugin(plugin.PluginBase):
    """websocket proxy plugin."""

    def _genReq(self):

        rsa = RSA.gen_key(
            self.environment[owspcons.ConfigEnv.KEY_SIZE],
            65537,
        )
        rsapem = rsa.as_pem(cipher=None)
        evp = EVP.PKey()
        evp.assign_rsa(rsa)
        rsa = None  # should not be freed here
        req = X509.Request()
        req.set_pubkey(evp)
        req.sign(evp, 'sha1')
        return rsapem, req.as_pem(), req.get_pubkey().as_pem(cipher=None)

    def __init__(self, context):
        super(Plugin, self).__init__(context=context)
        self._enabled = False
        self._need_key = False
        self._need_cert = False
        self._on_separate_h = False
        self._csr_file = None

    @plugin.event(
        stage=plugin.Stages.STAGE_INIT,
    )
    def _init(self):
        self.environment.setdefault(
            owspcons.ConfigEnv.WSP_CERTIFICATE_CHAIN,
            None
        )
        self.environment.setdefault(
            owspcons.ConfigEnv.REMOTE_ENGINE_CER,
            None
        )
        self.environment.setdefault(
            owspcons.ConfigEnv.KEY_SIZE,
            owspcons.Defaults.DEFAULT_KEY_SIZE
        )

    @plugin.event(
        stage=plugin.Stages.STAGE_VALIDATION,
    )
    def _validate(self):
        self._enabled = self.environment[
            owspcons.ConfigEnv.WEBSOCKET_PROXY_CONFIG
        ]

    @plugin.event(
        stage=plugin.Stages.STAGE_MISC,
        condition=lambda self: (
            self._enabled
        ),
        after=(
            owspcons.Stages.CA_AVAILABLE,
        ),
    )
    def _misc_pki(self):

        self._need_cert = not os.path.exists(
            owspcons.FileLocations.
            OVIRT_ENGINE_PKI_WEBSOCKET_PROXY_CERT
        )

        self._need_key = not os.path.exists(
            owspcons.FileLocations.
            OVIRT_ENGINE_PKI_WEBSOCKET_PROXY_KEY
        )

        self._on_separate_h = not os.path.exists(
            owspcons.FileLocations.
            OVIRT_ENGINE_PKI_ENGINE_CERT
        )

        inline = True
        my_pubk = None
        if self._need_key:
            wspkey, req, my_pubk = self._genReq()

            inline = dialog.queryBoolean(
                dialog=self.dialog,
                name='OVESETUP_CSR_INLINE',
                note=_(
                    '\nDo you prefer to manage certificate signing request '
                    'and response\n'
                    'inline or thought support files? '
                    '(@VALUES@) [@DEFAULT@]: '
                ),
                prompt=True,
                true=_('Inline'),
                false=_('Files'),
                default=True,
            )

            if inline:
                self.dialog.displayMultiString(
                    name=owspcons.Displays.CERTIFICATE_REQUEST,
                    value=req.splitlines(),
                    note=_(
                        '\nPlease issue WebSocket Proxy certificate based '
                        'on this certificate request\n\n'
                    ),
                )
            else:
                self._csr_file = tempfile.NamedTemporaryFile(
                    mode='w',
                    delete=False,
                )
                self._csr_file.write(req)
                self._csr_file.close()
                self.dialog.note(
                    text=_(
                        "\nThe certificate signing request is available at:\n"
                        "{fname}\n\n"
                    ).format(
                        fname=self._csr_file.name,
                    ),
                )
            self.environment[otopicons.CoreEnv.MAIN_TRANSACTION].append(
                filetransaction.FileTransaction(
                    name=owspcons.FileLocations.
                    OVIRT_ENGINE_PKI_WEBSOCKET_PROXY_KEY,
                    mode=0o600,
                    owner=self.environment[osetupcons.SystemEnv.USER_ENGINE],
                    enforcePermissions=True,
                    content=wspkey,
                    modifiedList=self.environment[
                        otopicons.CoreEnv.MODIFIED_FILES
                    ],
                )
            )

        if self._need_cert:
            self.dialog.note(
                text=_(
                    "\nEnroll SSL certificate for the websocket proxy "
                    "service.\n"
                    "It can be done using engine internal CA, if no 3rd "
                    "party CA is available,\n"
                    "with this sequence:\n\n"
                    "1. Copy and save certificate request at\n"
                    "    /etc/pki/ovirt-engine/requests/{name}-{fqdn}.req\n"
                    "on the engine host\n\n"
                    "2. execute, on the engine host, this command "
                    "to enroll the cert:\n"
                    " /usr/share/ovirt-engine/bin/pki-enroll-request.sh \\\n"
                    "     --name={name}-{fqdn} \\\n"
                    "     --subject=\"/C=<country>/O=<organization>/"
                    "CN={fqdn}\"\n"
                    "Substitute <country>, <organization> to suite your "
                    "environment\n"
                    "(i.e. the values must match values in the "
                    "certificate authority of your engine)\n\n"
                ).format(
                    fqdn=self.environment[osetupcons.ConfigEnv.FQDN],
                    name=owspcons.Const.WEBSOCKET_PROXY_CERT_NAME,
                ),
            )

            if inline:
                self.dialog.note(
                    text=_(
                        "3. Certificate will be available at\n"
                        "    /etc/pki/ovirt-engine/certs/{name}-{fqdn}.cer\n"
                        "on the engine host, please paste that content here "
                        "when required\n"
                    ).format(
                        fqdn=self.environment[osetupcons.ConfigEnv.FQDN],
                        name=owspcons.Const.WEBSOCKET_PROXY_CERT_NAME,
                    ),
                )
            else:
                self.dialog.note(
                    text=_(
                        "3. Certificate will be available at\n"
                        "    /etc/pki/ovirt-engine/certs/{name}-{fqdn}.cer\n"
                        "on the engine host, please copy that file here "
                        "and provide its location when required\n"
                    ).format(
                        fqdn=self.environment[osetupcons.ConfigEnv.FQDN],
                        name=owspcons.Const.WEBSOCKET_PROXY_CERT_NAME,
                    ),
                )

            goodcert = False
            while not goodcert:
                if inline:
                    self.environment[
                        owspcons.ConfigEnv.WSP_CERTIFICATE_CHAIN
                    ] = '\n'.join(
                        self.dialog.queryMultiString(
                            name=owspcons.ConfigEnv.WSP_CERTIFICATE_CHAIN,
                            note=_(
                                '\nPlease input WSP certificate chain that '
                                'matches certificate request,\n'
                                '(issuer is not mandatory, from intermediate'
                                ' and upper)\n\n'
                            ),
                        )
                    )
                else:
                    goodfile = False
                    while not goodfile:
                        filename = self.dialog.queryString(
                            name='WSP_T_CERT_FILENAME',
                            note=_(
                                '\nPlease input the location of the file '
                                'where you copied\n'
                                'back the signed cert on this host: '
                            ),
                            prompt=True,
                        )
                        try:
                            with open(filename) as f:
                                self.environment[
                                    owspcons.ConfigEnv.WSP_CERTIFICATE_CHAIN
                                ] = f.read()
                                goodfile = True
                        except EnvironmentError:
                            self.logger.error(
                                _(
                                    'Error reading {fname}, '
                                    'please try again'
                                ).format(
                                    fname=filename,
                                )
                            )
                try:
                    if my_pubk == X509.load_cert_string(
                        self.environment[
                            owspcons.ConfigEnv.WSP_CERTIFICATE_CHAIN
                        ]
                    ).get_pubkey().as_pem(cipher=None):
                        goodcert = True
                    else:
                        self.logger.error(
                            _(
                                'The cert you provided doesn\'t '
                                'match the required CSR.\n'
                                'Please try again'
                            )
                        )
                except X509.X509Error:
                    self.logger.error(
                        _(
                            'The cert you provided is invalid.\n'
                            'Please try again'
                        )
                    )

            self.environment[otopicons.CoreEnv.MAIN_TRANSACTION].append(
                filetransaction.FileTransaction(
                    name=owspcons.FileLocations.
                    OVIRT_ENGINE_PKI_WEBSOCKET_PROXY_CERT,
                    mode=0o600,
                    owner=self.environment[osetupcons.SystemEnv.USER_ENGINE],
                    enforcePermissions=True,
                    content=self.environment[
                        owspcons.ConfigEnv.WSP_CERTIFICATE_CHAIN
                    ],
                    modifiedList=self.environment[
                        otopicons.CoreEnv.MODIFIED_FILES
                    ],
                )
            )

        if self._on_separate_h:
            self.logger.debug('Acquiring engine.crt from the engine')
            while not self.environment[
                owspcons.ConfigEnv.REMOTE_ENGINE_CER
            ]:
                remote_engine_host = self.dialog.queryString(
                    name='REMOTE_ENGINE_HOST',
                    note=_(
                        'Please provide the FQDN or IP '
                        'of the remote engine host: '
                    ),
                    prompt=True,
                )

                with contextlib.closing(
                    urllib2.urlopen(
                        'http://{engine_fqdn}/ovirt-engine/services/'
                        'pki-resource?resource=engine-certificate&'
                        'format=X509-PEM'.format(
                            engine_fqdn=remote_engine_host
                        )
                    )
                ) as urlObj:
                    engine_cer = urlObj.read()
                    if engine_cer:
                        self.environment[
                            owspcons.ConfigEnv.REMOTE_ENGINE_CER
                        ] = engine_cer

            self.environment[
                otopicons.CoreEnv.MAIN_TRANSACTION
            ].append(
                filetransaction.FileTransaction(
                    name=owspcons.FileLocations.
                    OVIRT_ENGINE_PKI_ENGINE_CERT,
                    mode=0o600,
                    owner=self.environment[
                        osetupcons.SystemEnv.USER_ENGINE
                    ],
                    enforcePermissions=True,
                    content=self.environment[
                        owspcons.ConfigEnv.REMOTE_ENGINE_CER
                    ],
                    modifiedList=self.environment[
                        otopicons.CoreEnv.MODIFIED_FILES
                    ],
                )
            )

    @plugin.event(
        stage=plugin.Stages.STAGE_CLEANUP,
    )
    def _cleanup(self):
        if self._csr_file is not None:
            if os.path.exists(self._csr_file.name):
                os.unlink(self._csr_file.name)


# vim: expandtab tabstop=4 shiftwidth=4
