#
# vnc.py: VNC related installer functionality
#
# Copyright (C) 2004, 2007  Red Hat, Inc.  All rights reserved.
#
# 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 should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Author(s): Jeremy Katz <katzj@redhat.com>
#

import os, sys, string
import time
from snack import *
from constants import *
from constants_text import *
import network
import isys
import product
import socket
import subprocess

import gettext
_ = lambda x: gettext.ldgettext("anaconda", x)
P_ = lambda x, y, z: gettext.ldngettext("anaconda", x, y, z)

import logging
log = logging.getLogger("anaconda")
stdoutLog = logging.getLogger("anaconda.stdout")

class VncServer:

    def __init__(self, display="1", root="/", ip=None, name=None,
                desktop="Desktop", password="", vncconnecthost="",
                vncconnectport="", log_file="/tmp/vncserver.log",
                pw_file="/tmp/vncpassword", pw_init_file="/tmp/vncpassword.dat"):
        self.display = display
        self.root = root
        self.ip = ip
        self.name = name
        self.desktop = desktop
        self.password = password
        self.vncconnecthost = vncconnecthost
        self.vncconnectport = vncconnectport
        self.log_file = log_file
        self.pw_file = pw_file
        self.pw_init_file = pw_init_file
        self.connxinfo = None
        self.anaconda = None
        self.log = logging.getLogger("anaconda.stdout")

    def recoverVNCPassword(self):
        """Rescue the vncpassword from where loader left it 

        We are not to check for validity yet, if there is a file
        pass it to the variable, if there is not, set the var 
        to ''. We will check valididty later.
        """
        # see if there is a vnc password file
        try:
            pfile = open(self.pw_init_file, "r")
            self.password=pfile.readline().strip()
            pfile.close()
            os.unlink(self.pw_init_file)
        except:
            self.password=""

    def setVNCPassword(self):
        """Change the vnc server password. Output to file. """

        if len(self.password) == 0:
            self.setVNCParam("SecurityTypes", "None")
            self.setVNCParam("rfbauth","0")
            return

        # If there is a password the SecurityTypes = VncAuth for all connections.
        self.setVNCParam("SecurityTypes", "VncAuth")
        self.setVNCParam("rfbauth",self.pw_file)

        # password input combination.
        pwinput = "%s\n%s\n" % (self.password, self.password)
        vnccommand = [self.root+"/usr/bin/vncpasswd", self.pw_file]
        vncpswdo = subprocess.Popen(vnccommand, stdin=subprocess.PIPE, stdout=subprocess.PIPE)# We pipe the output
                                                                                              # so the user does not see it.
        (out, err) = vncpswdo.communicate(input=pwinput)
        return vncpswdo.returncode

    def initialize(self):
        """Here is were all the relative vars get initialized. """

        # see if we can sniff out network info
        netinfo = network.Network()

        devices = netinfo.netdevices
        active_devs = network.getActiveNetDevs()

        if active_devs != []:
            dev = devices[active_devs[0]]

            try:
                self.ip = isys.getIPAddress(dev.get("DEVICE"))
                log.info("ip of %s is %s" % (dev.get("DEVICE"), self.ip))

                if self.ip == "127.0.0.1" or self.ip == "::1":
                    self.ip = None
            except Exception, e:
                log.warning("Got an exception trying to get the self.ip addr "
                            "of %s: %s" % (dev.get("DEVICE"), e))
        else:
            self.ip = None

        self.name = network.getDefaultHostname(self.anaconda)
        ipstr = self.ip

        if self.ip.find(':') != -1:
            ipstr = "[%s]" % (self.ip,)

        if (self.name is not None) and (not self.name.startswith('localhost')) and (ipstr is not None):
            self.connxinfo = "%s:%s (%s)" % (socket.getfqdn(name=self.name), self.display, ipstr,)
        elif ipstr is not None:
            self.connxinfo = "%s:%s" % (ipstr, self.display,)
        else:
            self.connxinfo = None

        # figure out product info
        if self.name is not None:
            self.desktop = _("%(productName)s %(productVersion)s installation "
                             "on host %(name)s") \
                           % {'productName': product.productName,
                              'productVersion': product.productVersion,
                              'name': self.name}
        else:
            self.desktop = _("%(productName)s %(productVersion)s installation")\
                           % {'productName': product.productName,
                              'productVersion': product.productVersion}

    def setVNCParam(self, param, value):
        """Set a parameter in the Xvnc server. 

        Possible values for param and value. param=(values)
        SecurityTypes=(VncAuth,None)
        """
        vncconfigcommand = [self.root+"/usr/bin/vncconfig", "-display", ":%s"%self.display , "-set" , "%s=%s" %(param, value)]
        vncconfo = subprocess.Popen(vncconfigcommand)# we dont want output
        return vncconfo.returncode

    def openlogfile(self):
        try:
            err = os.open(self.log_file, os.O_RDWR | os.O_CREAT)
            if err < 0:
                sys.stderr.write("error opening %s\n", log)
                return None
            else:
                return err
        except:
                return None

    def connectToView(self):
        """Attempt to connect to self.vncconnecthost"""

        maxTries = 10
        self.log.info(_("Attempting to connect to vnc client on host %s...") % (self.vncconnecthost,))

        if self.vncconnectport != "":
            hostarg = self.vncconnecthost + ":" + self.vncconnectport
        else:
            hostarg = self.vncconnecthost

        vncconfigcommand = [self.root+"/usr/bin/vncconfig", "-display", ":%s"%self.display, "-connect", hostarg]

        for i in range(maxTries):
            vncconfp = subprocess.Popen(vncconfigcommand, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # vncconfig process
            (out, err) = vncconfp.communicate()

            if err == '':
                self.log.info(_("Connected!"))
                return True
            elif err.startswith("connecting") and err.endswith("failed\n"):
                self.log.info(_("Will try to connect again in 15 seconds..."))
                time.sleep(15)
                continue
            else:
                log.critical(err)
                sys.exit(1)
        self.log.error(P_("Giving up attempting to connect after %d try!\n",
                          "Giving up attempting to connect after %d tries!\n",
                          maxTries) % (maxTries,))
        return False

    def VNCListen(self):
        """Put the server in listening mode.

        We dont really have to do anything for the server to listen :)
        """
        if self.connxinfo != None:
            self.log.info(_("Please manually connect your vnc client to %s to begin the install.") % (self.connxinfo,))
        else:
            self.log.info(_("Please manually connect your vnc client to begin the install."))

    def startServer(self):
        self.log.info(_("Starting VNC..."))

        # Lets call it from here for now.
        self.initialize()

        # Lets start the xvnc regardless of vncconnecthost and password.
        # We can change the configuration on the fly later.
        xvnccommand =  [ self.root + "/usr/bin/Xvnc", ":%s" % self.display, "-nevershared",
                        "-depth", "16", "-geometry", "800x600", "-br",
                        "IdleTimeout=0", "-auth", "/dev/null", "-once",
                        "DisconnectClients=false", "desktop=%s" % (self.desktop,),
                        "SecurityTypes=None"]
        try:
            xvncp = subprocess.Popen(xvnccommand, stdout=self.openlogfile(), stderr=subprocess.STDOUT)
        except:
            stdoutLog.critical("Could not start the VNC server.  Aborting.")
            sys.exit(1)

        # Lets give the xvnc time to initialize
        time.sleep(1)

        # Make sure it hasn't blown up
        if xvncp.poll() != None:
            sys.exit(1)
        else:
            self.log.info(_("The VNC server is now running."))

        # Lets look at the password stuff
        if self.password == "":
            pass
        elif len(self.password) < 6:
            self.changeVNCPasswdWindow()

        # Create the password file.
        self.setVNCPassword()

        # Lets tell the user what we are going to do.
        if self.vncconnecthost != "":
            self.log.warning(_("\n\nYou chose to connect to a listening vncviewer. \n"
                                "This does not require a password to be set.  If you \n"
                                "set a password, it will be used in case the connection \n"
                                "to the vncviewer is unsuccessful\n\n"))
        elif self.password == "":
             self.log.warning(_("\n\nWARNING!!! VNC server running with NO PASSWORD!\n"
                                "You can use the vncpassword=<password> boot option\n"
                                "if you would like to secure the server.\n\n"))
        elif self.password != "":
             self.log.warning(_("\n\nYou chose to execute vnc with a password. \n\n"))
        else:
             self.log.warning(_("\n\nUnknown Error.  Aborting. \n\n"))
             sys.exit(1)

        # Lets try to configure the vnc server to whatever the user specified
        if self.vncconnecthost != "":
            connected = self.connectToView()
            if not connected:
                self.VNCListen()
        else:
            self.VNCListen()

        os.environ["DISPLAY"]=":%s" % self.display

    def changeVNCPasswdWindow(self):
        """ Change the password to a sane parameter.

        We ask user to input a password that len(password) > 6
        or password == ''. Have to find a way to put askVncWindow
        and this method together.
        """

        screen = SnackScreen()
        grid = GridFormHelp(screen, _("VNC Configuration"),"vnc", 1, 10)

        bb = ButtonBar(screen, (TEXT_OK_BUTTON,
                                (_("No password"), "nopass")))

        text = _("A password will prevent unauthorized listeners "
                 "connecting and monitoring your installation progress.  "
                 "Please enter a password to be used for the installation")
        grid.add(TextboxReflowed(40, text), 0, 0, (0, 0, 0, 1))

        entry1 = Entry (16, password = 1)
        entry2 = Entry (16, password = 1)
        passgrid = Grid (2, 2)
        passgrid.setField (Label (_("Password:")), 0, 0, (0, 0, 1, 0), anchorLeft = 1)
        passgrid.setField (Label (_("Password (confirm):")), 0, 1, (0, 0, 1, 0), anchorLeft = 1)
        passgrid.setField (entry1, 1, 0)
        passgrid.setField (entry2, 1, 1)
        grid.add (passgrid, 0, 1, (0, 0, 0, 1))

        grid.add(bb, 0, 8, (0, 1, 1, 0), growx = 1)

        while 1:
            res = grid.run()
            rc = bb.buttonPressed(res)

            if rc == "nopass":
                screen.finish()
                return ""
            else:
                pw = entry1.value()
                cf = entry2.value()
                if pw != cf:
                    ButtonChoiceWindow(screen, _("Password Mismatch"),
                                       _("The passwords you entered were "
                                         "different. Please try again."),
                                       buttons = [ TEXT_OK_BUTTON ],
                                       width = 50)
                elif len(pw) < 6:
                    ButtonChoiceWindow(screen, _("Password Length"),
                                       _("The password must be at least "
                                         "six characters long."),
                                       buttons = [ TEXT_OK_BUTTON ],
                                       width = 50)
                else:
                    screen.finish()
                    self.password = pw
                    return 

                entry1.set("")
                entry2.set("")
                continue
            continue

def askVncWindow(title = None, message = None):
    if not os.access('/usr/bin/Xvnc', os.X_OK):
        return -1

    if not network.hasActiveNetDev():
        return -1

    if not title:
        title = _("Unable to Start X")
    if not message:
        message = _("X was unable to start on your "
                    "machine.  Would you like to "
                    "start VNC to connect to "
                    "this computer from another "
                    "computer and perform a "
                    "graphical install or continue "
                    "with a text mode install?")

    screen = SnackScreen()
    vncpass = None
    vncconnect = 0

    STEP_MESSAGE = 0
    STEP_PASS = 1
    STEP_DONE = 3
    step = 0
    while step < STEP_DONE:
        if step == STEP_MESSAGE:
            button = ButtonChoiceWindow(screen, title, message,
                                        buttons = [ _("Start VNC"),
                                                    _("Use text mode") ])

	    if button == string.lower (_("Use text mode")):
                screen.finish()
                return -1
            else:
                step = STEP_PASS
                continue

        if step == STEP_PASS:
            grid = GridFormHelp(screen, _("VNC Configuration"),
                                "vnc", 1, 10)

            bb = ButtonBar(screen, (TEXT_OK_BUTTON,
                                    (_("No password"), "nopass"),
                                    TEXT_BACK_BUTTON))

            text = _("A password will prevent unauthorized listeners "
                     "connecting and monitoring your installation progress.  "
                     "Please enter a password to be used for the installation")
            grid.add(TextboxReflowed(40, text), 0, 0, (0, 0, 0, 1))

            entry1 = Entry (16, password = 1)
            entry2 = Entry (16, password = 1)
            passgrid = Grid (2, 2)
            passgrid.setField (Label (_("Password:")), 0, 0, (0, 0, 1, 0), anchorLeft = 1)
            passgrid.setField (Label (_("Password (confirm):")), 0, 1, (0, 0, 1, 0), anchorLeft = 1)
            passgrid.setField (entry1, 1, 0)
            passgrid.setField (entry2, 1, 1)
            grid.add (passgrid, 0, 1, (0, 0, 0, 1))

            grid.add(bb, 0, 8, (0, 1, 1, 0), growx = 1)

            while 1:
                res = grid.run()
                rc = bb.buttonPressed(res)

                if rc == TEXT_BACK_CHECK:
                    screen.popWindow()
                    step = STEP_MESSAGE
                    break
                elif rc == "nopass":
                    screen.finish()
                    return None
                else:
                    pw = entry1.value()
                    cf = entry2.value()
                    if pw != cf:
                        ButtonChoiceWindow(screen, _("Password Mismatch"),
                                           _("The passwords you entered were "
                                             "different. Please try again."),
                                           buttons = [ TEXT_OK_BUTTON ],
                                           width = 50)
                    elif len(pw) < 6:
                        ButtonChoiceWindow(screen, _("Password Length"),
                                           _("The password must be at least "
                                             "six characters long."),
                                           buttons = [ TEXT_OK_BUTTON ],
                                           width = 50)
                    else:
                        screen.finish()
                        return pw

                    entry1.set("")
                    entry2.set("")
                    continue
                continue

    screen.finish()
    return -1

if __name__ == "__main__":
    askVncWindow()