#
#

# Copyright (C) 2006, 2007 Google Inc.
#
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.


"""Module encapsulating ssh functionality.

"""


import os

from ganeti import logger
from ganeti import utils
from ganeti import errors
from ganeti import constants


KNOWN_HOSTS_OPTS = [
  "-oGlobalKnownHostsFile=%s" % constants.SSH_KNOWN_HOSTS_FILE,
  "-oUserKnownHostsFile=/dev/null",
  ]

# Note: BATCH_MODE conflicts with ASK_KEY
BATCH_MODE_OPTS = [
  "-oEscapeChar=none",
  "-oBatchMode=yes",
  "-oStrictHostKeyChecking=yes",
  ]

ASK_KEY_OPTS = [
  "-oStrictHostKeyChecking=ask",
  "-oEscapeChar=none",
  "-oHashKnownHosts=no",
  ]


def GetUserFiles(user, mkdir=False):
  """Return the paths of a user's ssh files.

  The function will return a triplet (priv_key_path, pub_key_path,
  auth_key_path) that are used for ssh authentication. Currently, the
  keys used are DSA keys, so this function will return:
  (~user/.ssh/id_dsa, ~user/.ssh/id_dsa.pub,
  ~user/.ssh/authorized_keys).

  If the optional parameter mkdir is True, the ssh directory will be
  created if it doesn't exist.

  Regardless of the mkdir parameters, the script will raise an error
  if ~user/.ssh is not a directory.

  """
  user_dir = utils.GetHomeDir(user)
  if not user_dir:
    raise errors.OpExecError("Cannot resolve home of user %s" % user)

  ssh_dir = os.path.join(user_dir, ".ssh")
  if not os.path.lexists(ssh_dir):
    if mkdir:
      try:
        os.mkdir(ssh_dir, 0700)
      except EnvironmentError, err:
        raise errors.OpExecError("Can't create .ssh dir for user %s: %s" %
                                 (user, str(err)))
  elif not os.path.isdir(ssh_dir):
    raise errors.OpExecError("path ~%s/.ssh is not a directory" % user)

  return [os.path.join(ssh_dir, base)
          for base in ["id_dsa", "id_dsa.pub", "authorized_keys"]]


class SshRunner:
  """Wrapper for SSH commands.

  """
  def BuildCmd(self, hostname, user, command, batch=True, ask_key=False):
    """Build an ssh command to execute a command on a remote node.

    Args:
      hostname: the target host, string
      user: user to auth as
      command: the command
      batch: if true, ssh will run in batch mode with no prompting
      ask_key: if true, ssh will run with StrictHostKeyChecking=ask, so that
               we can connect to an unknown host (not valid in batch mode)

    Returns:
      The ssh call to run 'command' on the remote host.

    """
    argv = ["ssh", "-q"]
    argv.extend(KNOWN_HOSTS_OPTS)
    if batch:
      # if we are in batch mode, we can't ask the key
      if ask_key:
        raise errors.ProgrammerError("SSH call requested conflicting options")
      argv.extend(BATCH_MODE_OPTS)
    elif ask_key:
      argv.extend(ASK_KEY_OPTS)
    argv.extend(["%s@%s" % (user, hostname), command])
    return argv

  def Run(self, hostname, user, command, batch=True, ask_key=False):
    """Runs a command on a remote node.

    This method has the same return value as `utils.RunCmd()`, which it
    uses to launch ssh.

    Args:
      hostname: the target host, string
      user: user to auth as
      command: the command
      batch: if true, ssh will run in batch mode with no prompting
      ask_key: if true, ssh will run with StrictHostKeyChecking=ask, so that
               we can connect to an unknown host (not valid in batch mode)

    Returns:
      `utils.RunResult` like `utils.RunCmd()`

    """
    return utils.RunCmd(self.BuildCmd(hostname, user, command, batch=batch,
                                      ask_key=ask_key))

  def CopyFileToNode(self, node, filename):
    """Copy a file to another node with scp.

    Args:
      node: node in the cluster
      filename: absolute pathname of a local file

    Returns:
      success: True/False

    """
    if not os.path.isfile(filename):
      logger.Error("file %s does not exist" % (filename))
      return False

    if not os.path.isabs(filename):
      logger.Error("file %s must be an absolute path" % (filename))
      return False

    command = ["scp", "-q", "-p"]
    command.extend(KNOWN_HOSTS_OPTS)
    command.extend(BATCH_MODE_OPTS)
    command.append(filename)
    command.append("%s:%s" % (node, filename))

    result = utils.RunCmd(command)

    if result.failed:
      logger.Error("copy to node %s failed (%s) error %s,"
                   " command was %s" %
                   (node, result.fail_reason, result.output, result.cmd))

    return not result.failed

  def VerifyNodeHostname(self, node):
    """Verify hostname consistency via SSH.

    This functions connects via ssh to a node and compares the hostname
    reported by the node to the name with have (the one that we
    connected to).

    This is used to detect problems in ssh known_hosts files
    (conflicting known hosts) and incosistencies between dns/hosts
    entries and local machine names

    Args:
      node: nodename of a host to check. can be short or full qualified hostname

    Returns:
      (success, detail)
      where
        success: True/False
        detail: String with details

    """
    retval = self.Run(node, 'root', 'hostname')

    if retval.failed:
      msg = "ssh problem"
      output = retval.output
      if output:
        msg += ": %s" % output
      return False, msg

    remotehostname = retval.stdout.strip()

    if not remotehostname or remotehostname != node:
      return False, "hostname mismatch, got %s" % remotehostname

    return True, "host matches"


def WriteKnownHostsFile(cfg, sstore, file_name):
  """Writes the cluster-wide equally known_hosts file.

  """
  utils.WriteFile(file_name, mode=0700,
                  data="%s ssh-rsa %s\n" % (sstore.GetClusterName(),
                                            cfg.GetHostKey()))

@@ -1114,44 +1114,44 @@
  %22-o
-EscapeChar=none%22,%0A  %22-oBatchMode=yes
+BatchMode=yes%22,%0A  %22-oEscapeChar=none
 %22,%0A 
@@ -1208,41 +1208,8 @@
 = %5B%0A
-  %22-oStrictHostKeyChecking=ask%22,%0A
   %22-
@@ -1252,16 +1252,49 @@
 ts=no%22,%0A
+  %22-oStrictHostKeyChecking=ask%22,%0A
   %5D%0A%0A%0Ade

Order SSH options alphabetically