#!/usr/bin/python
#
# @package      hubzero-mw-client
# @file         maxwell
# @author       Rick Kennell <kennell@purdue.edu>
# @author       Nicholas J. Kisseberth <nkissebe@purdue.edu>
# @copyright    Copyright (c) 2005-2015 HUBzero Foundation, LLC.
# @license      http://opensource.org/licenses/MIT MIT
#
# Copyright (c) 2005-2015 HUBzero Foundation, LLC.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# HUBzero is a registered trademark of HUBzero Foundation, LLC.
#

import MySQLdb
import os
import sys
import signal
import time
import pwd
import re
import json 

PATH_REGEXP = r'\A[ a-zA-Z0-9_\/.\-\\]+\Z' 
GEOM_REGEXP = r'\A\d+x\d+\Z'

# We always run here:
os.chdir("/")

logfile = None
master_logfile_name = "/var/log/mw-client/master"

applet='VncViewer-20140116-01.jar'
signedapplet='SignedVncViewer-20140116-01.jar'

submit_app_list = {}

#=============================================================================
# Load default parameters...
#=============================================================================
mysql_host=""
mysql_user=""
mysql_password=""
mysql_db=""
mysql_connect_attempts=120
mysql_tooltable="jos_tool_version"
mysql_hosttable="jos_tool_version_hostreq"

passfile_template="/tmp/vncpasswd.%d"
session_suffix=""
display_retries=4
homedir_retries=10
default_stats_mode="notify"
get_stats_retries=10
get_stats_delay=60
web_homedir="/var/www"
filexfer_decoration=""
visualization_params=""
default_vnc_timeout=86400
default_vnc_depth=24
default_vnc_hostreq_arr=['sessions']
default_version='current'
vnc_proxy_port=443
mw_user_homedir="/var/www"
hub_name=""
hub_url=""
hub_homedir=""
hub_template=""
json_results=False
wsproxy_port=8080
wsproxy_host=''
wsproxy_encrypt=False

if os.path.exists("/etc/debian_version"):
  mw_user="www-data"
elif os.path.exists("/etc/redhat-release"):
  mw_user="apache"
else:
  print "Unable to determine distribution"
  sys.exit(1)

#=============================================================================
# Load the configuration and override the variables above.
#=============================================================================
try:
  execfile('/etc/mw-client/mw-client.conf')
except IOError, (errno, strerror):
  print "I/O error(%s): %s" % (errno, strerror)
  print "The configuration file /etc/mw-client/mw-client.conf needs to exist and be readable by user '%s'" % mw_user
  os._exit(1)

#=============================================================================
# Set up errors to go to the log file.
#=============================================================================
def openlog(logfile_name):
  global logfile

  logfile = sys.stderr

  if os.isatty(2):
    return

  for fd in range(2,1024):
    try:
      os.close(fd)
    except:
      pass

  try:
    fd = os.open(logfile_name, os.O_CREAT|os.O_APPEND|os.O_WRONLY, 0644)
    if fd != 2:
      os.dup2(fd,2)
      os.close(fd)
  except:
    pass

#=============================================================================
# Fork a dissociated process.  Return 1 for child.  Return 0 for parent.
#=============================================================================
def background():
  sys.stdout.flush()
  sys.stderr.flush()

  # Dissociate from the parent so that it doesn't wait.
  # Use the double-fork trick.
  try:
    pid = os.fork()
    if pid > 0:
      return 0  # Indicate we're a parent.
  except OSError, e:
    log("fork #1 failed: %d (%s)" % (e.errno, e.strerror))
    os._exit(1)

  # This is the child.  Dissociate from the parent.
  os.setsid()
  sys.stdout.flush()
  sys.stderr.flush()
  os.dup2(2,1)

  try:
    pid = os.fork()
    if pid > 0:
      #log("Second parent is %d" % pid)
      os._exit(0)
  except OSError, e:
    log("fork #2 failed: %d (%s)" % (e.errno, e.strerror))
    os._exit(1)

  # At this point, this is a child of a child.  Return.
  return 1  # Indicate we're a child.

#=============================================================================
# Dissociate the process from a child.
#=============================================================================
def dissociate():
  if not background():
    os._exit(0)

#=============================================================================
# Create database connection.
#=============================================================================
def db_connect():
  for x in range(0,mysql_connect_attempts):
    try:
      db = MySQLdb.connect(host=mysql_host, user=mysql_user, passwd=mysql_password, db=mysql_db)
      #log("db_connect finished on iteration %d" % x)
      return db
    except:
      log("Exception in db_connect")
    time.sleep(1)

#=============================================================================
# Log a message.
#=============================================================================
def log(msg):
  if (os.isatty(1)):
    print msg
  else:
    timestamp = "[" + time.strftime("%H:%M:%S") + "] "
    logfile.write(timestamp + msg + "\n")
    logfile.flush()

#=============================================================================
# Create SSH notify keys.
#=============================================================================
def create_notify_keys(quiet):
  if quiet:
    status = os.system("echo 'y' | ssh-keygen -t rsa -f /etc/mw-client/notify.key -N '' -q")
  else:
    print "<b>Creating notify keys</b><br>"
    sys.stdout.flush()
    status = os.system("echo 'y' | ssh-keygen -t rsa -f /etc/mw-client/notify.key -N ''")
  if status != 0:
    print "<b>Unable to create notify keys.</b>\n"
    return
  try:
    home=os.environ["HOME"]
  except KeyError:
    print "Unable to determine web server home directory.  Using default.\n"
    home=web_homedir
  authkeys=home + "/.ssh/authorized_keys"
  akfile = os.open(authkeys, os.O_WRONLY | os.O_CREAT | os.O_APPEND)
  pubkey = os.open("/etc/mw-client/notify.key.pub", os.O_RDONLY)
  line = os.read(pubkey, 10000)
  line = """COMMAND="/usr/bin/maxwell notify" """ + line
  os.write(akfile, line)

#=============================================================================
# Check that the SSH notify key exists.
#=============================================================================
def check_notify_keys(quiet):
# if not quiet:
#   print "<b>Checking notify keys</b><br>"
  log("Checking notify keys")
  try:
    fd = os.open("/etc/mw-client/notify.key",os.O_RDONLY)
    os.close(fd)
  except OSError:
    create_notify_keys(quiet)

#=============================================================================
# Create SSH keys.
#=============================================================================
def create_ssh_keys(quiet):
  if quiet: 
    status = os.system("echo 'y' | ssh-keygen -t rsa -f /etc/mw-client/maxwell.key -N '' -q")
  else:
    print "<b>Creating ssh keys</b><br>"
    sys.stdout.flush()
    status = os.system("echo 'y' | ssh-keygen -t rsa -f /etc/mw-client/maxwell.key -N ''")
  if status != 0:
    print "<b>Unable to create ssh keys.</b><br>"

#=============================================================================
# Check that the SSH key exists.
#=============================================================================
def check_ssh_keys(quiet):
  try:
    fd = os.open("/etc/mw-client/maxwell.key",os.O_RDONLY)
    os.close(fd)
  except OSError:
    create_ssh_keys(quiet)

#=============================================================================
# Make sure that we know the remote host's key.
#=============================================================================
def check_ssh_host_key(host):
  cmd="""grep -q '%s[ ,]' ~/.ssh/known_hosts < /dev/null > /dev/null 2>&1""" % host
  status=os.system(cmd)
  if status == 0:
    return
  log("""Can't find key for %s.  Adding it to the known_hosts file.""" % host)
  status=os.system("""ssh-keyscan -t rsa %s >> ~/.ssh/known_hosts""" % host)
  if status == 0:
    log("Success.")
  else:
    log("Failure")

#=============================================================================
# Use ssh to run a command on a remote system.
#=============================================================================
def ssh(input,host,comm):
  if host == "":
    log("ERROR: Null host passed to ssh()")
  check_ssh_keys(1)
  check_ssh_host_key(host)

  if input=='':
    #print "No input<br>"
    status=os.system("""ssh -i /etc/mw-client/maxwell.key root@%s %s""" % (host,comm))
  else:
    #print "Input is %s<br>" % input
    #print "Command is %s<br>" % comm
    status=os.system("""cat %s | ssh -i /etc/mw-client/maxwell.key root@%s %s""" % (input,host,comm))
  return status

#=============================================================================
# Use ssh to run a command on a remote system.  Use I/O.
#=============================================================================
def iossh(host,comm):
  if host == "":
    log("ERROR: Null host passed to iossh()")
  check_ssh_keys(1)
  check_ssh_host_key(host)
  status=os.system("""ssh -i /etc/mw-client/maxwell.key root@%s %s""" % (host,comm))
  return status

#=============================================================================
# Use scp copy one or more files.
#=============================================================================
def scp(host,cmd):
  if host == "":
    log("ERROR: Null host passed to scp()")
  check_ssh_keys(1)
  check_ssh_host_key(host)
  status=os.system("""scp -i /etc/mw-client/maxwell.key %s""" % (cmd))
  return status

#=============================================================================
# Do an SSH key check on the specified host.
# Optionally, accept a new key.
#=============================================================================
def check_host(host,confirm):
  db = db_connect()
  c = db.cursor()

  arr = mysql(c,"""SELECT status
                   FROM host WHERE hostname='%s'""" % host)
  if len(arr) == 0:
    print "Unknown host '%s'<br>" % host
    log( "Unknown host '%s'<br>" % host)
    return 0

  row=arr[0]
  status=row[0]
  if status == 'up':
    mysql(c,"""UPDATE host SET status='down' WHERE hostname='%s'""" % host)
    return 0

  # Below, we might be trying to access the database from a strange
  # Python context.  Close the db here and reopen it when we need it.
  db.close()

  # Do all of these things BEFORE the iossh() call.
  # They will only confuse things if done in the context of forkpty().
  #
  check_notify_keys(0)
  check_ssh_keys(0)
  check_ssh_host_key(host)
  log( "Checking host %s" % host)

  try:
    (pid,fd) = os.forkpty()
  except:
    log("Exception in check_host: os.forkpty(): " + str(sys.exc_info()[0]))
    sys.exit(1)

  if pid == 0:
    status = iossh(host,"/usr/bin/maxwell_service check")
    if status != 0:
      print "Unable to exec ssh"
      log( "Unable to exec ssh")
      sys.exit(1)
    sys.exit(0)

  def alarmHandler(sig, frame):
    raise IOError, "Timeout"
    sys.exit(1)
  signal.signal(signal.SIGALRM, alarmHandler)
  signal.alarm(5)

  print "<br /><b>Checking host %s</b><br />" % host
  sys.stdout.flush()

  try:
    line = os.read(fd,1000)
  except:
    try:
      os.kill(9,pid)
    except:
      pass
    db = db_connect()
    c = db.cursor()
    mysql(c,"""UPDATE host SET status='sshfail' WHERE hostname='%s'""" % host)
    print "Unable to connect to %s.<br>" % (host)
    log( "Unable to connect to %s.<br>" % (host))
    print "You'll have to fix that.<br>"
    log( "You'll have to fix that.<br>")
    sys.exit(1)

  if line.startswith("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"):
    db = db_connect()
    c = db.cursor()
    mysql(c,"""UPDATE host SET status='sshfail' WHERE hostname='%s'""" % host)
    print "The host key has changed for %s.<br> %s" % (host,line)
    log( "The host key has changed for %s.<br> %s" % (host,line))
    print "You'll have to fix that.<br>"
    log( "You'll have to fix that.<br>")

  elif line.startswith("The authenticity of host"):
    if confirm=='yes':
      print "You need to confirm the host key.<br>"
      log( "You need to confirm the host key.<br>")
      os.write(fd,"yes" + "\n")
      os.read(fd,1000)
      db = db_connect()
      c = db.cursor()
      mysql(c,"""UPDATE host SET status='sshtest' WHERE hostname='%s'""" % host)
    else:
      print "Unable to confirm the host key.<br>"
      log( "Unable to confirm the host key.<br>")
      os.write(fd,"no" + "\n")
      os.read(fd,1000)
      db = db_connect()
      c = db.cursor()
      mysql(c,"""UPDATE host SET status='sshkey' WHERE hostname='%s'""" % host)

  elif (line.find("assword: ") != -1):
    print "There is no SSH key installed on %s.<br>" % host
    log( "There is no SSH key installed on %s.<br>" % host)
    os.kill(pid,9)
    db = db_connect()
    c = db.cursor()
    mysql(c,"""UPDATE host SET status='sshfail' WHERE hostname='%s'""" % host)
    return 1

  elif line.startswith("OK"):
    db = db_connect()
    c = db.cursor()
    res=mysql(c,"""UPDATE host SET status='up' WHERE hostname='%s'""" % host)
    print "Result is ", res
    log("Host check on '%s' was successful." % host)

  else:
    print "Unknown string: '%s'<br>" % line
    log( "Unknown string: '%s'<br>" % line)
    os.kill(pid,9)
    (pid, status) = os.waitpid(pid, 0)
    return 1

  try:
    (pid, status) = os.waitpid(pid, 0)
  except:
    pass

  (pid,fd) = os.forkpty()
  if pid < 0:
    print "Could not fork process: %d" % pid
    log( "Could not fork process: %d" % pid)
    sys.exit(1)
  if pid == 0:
    status=scp(host, "/etc/mw-client/notify.key root@%s:/etc/mw-client/notify.key" % host)
    sys.exit(status)

  print "Copying notify key to host %s\n" % host
  line = os.read(fd,1000)
  if len(line) != 0:
    print line

  try:
    (pid, status) = os.waitpid(pid, 0)
  except:
    pass

  db.close()
  return 0

#=============================================================================
# Generate a cleartext password.
#=============================================================================
def genpasswd(length,letters=None):
  if letters==None:
    letters="""abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_+=~;:'",.<>?/!@#$%^&*()[]{} """
  r = os.urandom(length*2)
  bytes=""
  for i in range(0,length):
    off = ord(r[2*i])*256+ord(r[2*i+1])
    off = off % len(letters)
    bytes += letters[off]
  return bytes

#print "Generated password is '%s'" % genpasswd(32)
#sys.exit(0)

#=============================================================================
# MySQL helpers
#=============================================================================
def mysql(c,cmd):
  try:
    count = c.execute(cmd)
    if c._warnings > 0:
      log("MySQL warning")
      log("SQL was: %s" % cmd)
    return c.fetchall()
  except MySQLdb.MySQLError, (num, expl):
    log("%s" % expl)
    log("SQL was: %s" % cmd)
    return ()
  except:
    log("Some other MySQL exception.")
    log("SQL was: %s" % cmd)
    return ()

#=============================================================================
# Execute a MySQL action that is not a query.  Return any error string.
#=============================================================================
def mysql_act(c,cmd):
  try:
    count = c.execute(cmd)
    return ""
  except MySQLdb.MySQLError, (num, expl):
    return expl

#=============================================================================
# Get a MySQL advisory lock.
#=============================================================================
def mysql_lock(c,name):
  iteration=0
  while 1:
    iteration += 1
    if iteration > 1:
      log("Contention while waiting on lock %s" % name)
    arr=()
    arr = mysql(c,"""SELECT get_lock('%s',5)""" % name)
    if len(arr) == 1:
      row = arr[0]
      status = row[0]
      if status == 1:
        return 1
    else:
      log("Error locking %s" % name)

#=============================================================================
# Release a MySQL advisory lock.
#=============================================================================
def mysql_unlock(c,name):
  arr = mysql(c,"SELECT release_lock('%s')" % name)
  if len(arr) == 1:
    row = arr[0]
    status = row[0]
    if status == 1:
      return 1
    else:
      log("Error unlocking '%s':  Status was '%s'." % str(status))
  else:
    log("Error unlocking %s" % name)

#=============================================================================
#=============================================================================
# VNC operations.
#=============================================================================
#=============================================================================

#=============================================================================
# Create a VNC password encoding.
#=============================================================================
def vncpasswd(passwd,passwd_file):

  keys = [690692107,   959852042,   1008472116,  219877899,  \
          422382336,   420946204,   153292807,   1058477350, \
          253165858,   909248553,   1058418737,  607723797,  \
          923533838,   941439032,   53085236,    958338305,  \
          1042157353,  369688836,   339616771,   421334066,  \
          354615608,   454498817,   221457937,   1042686480, \
          1041631756,  640031750,   1042023698,  138216962,  \
          822622977,   423559173,   1042225154,  237647920 ]

  SP1 = [ 0x01010400L, 0x00000000L, 0x00010000L, 0x01010404L, \
          0x01010004L, 0x00010404L, 0x00000004L, 0x00010000L, \
          0x00000400L, 0x01010400L, 0x01010404L, 0x00000400L, \
          0x01000404L, 0x01010004L, 0x01000000L, 0x00000004L, \
          0x00000404L, 0x01000400L, 0x01000400L, 0x00010400L, \
          0x00010400L, 0x01010000L, 0x01010000L, 0x01000404L, \
          0x00010004L, 0x01000004L, 0x01000004L, 0x00010004L, \
          0x00000000L, 0x00000404L, 0x00010404L, 0x01000000L, \
          0x00010000L, 0x01010404L, 0x00000004L, 0x01010000L, \
          0x01010400L, 0x01000000L, 0x01000000L, 0x00000400L, \
          0x01010004L, 0x00010000L, 0x00010400L, 0x01000004L, \
          0x00000400L, 0x00000004L, 0x01000404L, 0x00010404L, \
          0x01010404L, 0x00010004L, 0x01010000L, 0x01000404L, \
          0x01000004L, 0x00000404L, 0x00010404L, 0x01010400L, \
          0x00000404L, 0x01000400L, 0x01000400L, 0x00000000L, \
          0x00010004L, 0x00010400L, 0x00000000L, 0x01010004L ]

  SP2 = [ 0x80108020L, 0x80008000L, 0x00008000L, 0x00108020L, \
          0x00100000L, 0x00000020L, 0x80100020L, 0x80008020L, \
          0x80000020L, 0x80108020L, 0x80108000L, 0x80000000L, \
          0x80008000L, 0x00100000L, 0x00000020L, 0x80100020L, \
          0x00108000L, 0x00100020L, 0x80008020L, 0x00000000L, \
          0x80000000L, 0x00008000L, 0x00108020L, 0x80100000L, \
          0x00100020L, 0x80000020L, 0x00000000L, 0x00108000L, \
          0x00008020L, 0x80108000L, 0x80100000L, 0x00008020L, \
          0x00000000L, 0x00108020L, 0x80100020L, 0x00100000L, \
          0x80008020L, 0x80100000L, 0x80108000L, 0x00008000L, \
          0x80100000L, 0x80008000L, 0x00000020L, 0x80108020L, \
          0x00108020L, 0x00000020L, 0x00008000L, 0x80000000L, \
          0x00008020L, 0x80108000L, 0x00100000L, 0x80000020L, \
          0x00100020L, 0x80008020L, 0x80000020L, 0x00100020L, \
          0x00108000L, 0x00000000L, 0x80008000L, 0x00008020L, \
          0x80000000L, 0x80100020L, 0x80108020L, 0x00108000L ]

  SP3 = [ 0x00000208L, 0x08020200L, 0x00000000L, 0x08020008L, \
          0x08000200L, 0x00000000L, 0x00020208L, 0x08000200L, \
          0x00020008L, 0x08000008L, 0x08000008L, 0x00020000L, \
          0x08020208L, 0x00020008L, 0x08020000L, 0x00000208L, \
          0x08000000L, 0x00000008L, 0x08020200L, 0x00000200L, \
          0x00020200L, 0x08020000L, 0x08020008L, 0x00020208L, \
          0x08000208L, 0x00020200L, 0x00020000L, 0x08000208L, \
          0x00000008L, 0x08020208L, 0x00000200L, 0x08000000L, \
          0x08020200L, 0x08000000L, 0x00020008L, 0x00000208L, \
          0x00020000L, 0x08020200L, 0x08000200L, 0x00000000L, \
          0x00000200L, 0x00020008L, 0x08020208L, 0x08000200L, \
          0x08000008L, 0x00000200L, 0x00000000L, 0x08020008L, \
          0x08000208L, 0x00020000L, 0x08000000L, 0x08020208L, \
          0x00000008L, 0x00020208L, 0x00020200L, 0x08000008L, \
          0x08020000L, 0x08000208L, 0x00000208L, 0x08020000L, \
          0x00020208L, 0x00000008L, 0x08020008L, 0x00020200L ]

  SP4 = [ 0x00802001L, 0x00002081L, 0x00002081L, 0x00000080L, \
          0x00802080L, 0x00800081L, 0x00800001L, 0x00002001L, \
          0x00000000L, 0x00802000L, 0x00802000L, 0x00802081L, \
          0x00000081L, 0x00000000L, 0x00800080L, 0x00800001L, \
          0x00000001L, 0x00002000L, 0x00800000L, 0x00802001L, \
          0x00000080L, 0x00800000L, 0x00002001L, 0x00002080L, \
          0x00800081L, 0x00000001L, 0x00002080L, 0x00800080L, \
          0x00002000L, 0x00802080L, 0x00802081L, 0x00000081L, \
          0x00800080L, 0x00800001L, 0x00802000L, 0x00802081L, \
          0x00000081L, 0x00000000L, 0x00000000L, 0x00802000L, \
          0x00002080L, 0x00800080L, 0x00800081L, 0x00000001L, \
          0x00802001L, 0x00002081L, 0x00002081L, 0x00000080L, \
          0x00802081L, 0x00000081L, 0x00000001L, 0x00002000L, \
          0x00800001L, 0x00002001L, 0x00802080L, 0x00800081L, \
          0x00002001L, 0x00002080L, 0x00800000L, 0x00802001L, \
          0x00000080L, 0x00800000L, 0x00002000L, 0x00802080L ]

  SP5 = [ 0x00000100L, 0x02080100L, 0x02080000L, 0x42000100L, \
          0x00080000L, 0x00000100L, 0x40000000L, 0x02080000L, \
          0x40080100L, 0x00080000L, 0x02000100L, 0x40080100L, \
          0x42000100L, 0x42080000L, 0x00080100L, 0x40000000L, \
          0x02000000L, 0x40080000L, 0x40080000L, 0x00000000L, \
          0x40000100L, 0x42080100L, 0x42080100L, 0x02000100L, \
          0x42080000L, 0x40000100L, 0x00000000L, 0x42000000L, \
          0x02080100L, 0x02000000L, 0x42000000L, 0x00080100L, \
          0x00080000L, 0x42000100L, 0x00000100L, 0x02000000L, \
          0x40000000L, 0x02080000L, 0x42000100L, 0x40080100L, \
          0x02000100L, 0x40000000L, 0x42080000L, 0x02080100L, \
          0x40080100L, 0x00000100L, 0x02000000L, 0x42080000L, \
          0x42080100L, 0x00080100L, 0x42000000L, 0x42080100L, \
          0x02080000L, 0x00000000L, 0x40080000L, 0x42000000L, \
          0x00080100L, 0x02000100L, 0x40000100L, 0x00080000L, \
          0x00000000L, 0x40080000L, 0x02080100L, 0x40000100L ]

  SP6 = [ 0x20000010L, 0x20400000L, 0x00004000L, 0x20404010L, \
          0x20400000L, 0x00000010L, 0x20404010L, 0x00400000L, \
          0x20004000L, 0x00404010L, 0x00400000L, 0x20000010L, \
          0x00400010L, 0x20004000L, 0x20000000L, 0x00004010L, \
          0x00000000L, 0x00400010L, 0x20004010L, 0x00004000L, \
          0x00404000L, 0x20004010L, 0x00000010L, 0x20400010L, \
          0x20400010L, 0x00000000L, 0x00404010L, 0x20404000L, \
          0x00004010L, 0x00404000L, 0x20404000L, 0x20000000L, \
          0x20004000L, 0x00000010L, 0x20400010L, 0x00404000L, \
          0x20404010L, 0x00400000L, 0x00004010L, 0x20000010L, \
          0x00400000L, 0x20004000L, 0x20000000L, 0x00004010L, \
          0x20000010L, 0x20404010L, 0x00404000L, 0x20400000L, \
          0x00404010L, 0x20404000L, 0x00000000L, 0x20400010L, \
          0x00000010L, 0x00004000L, 0x20400000L, 0x00404010L, \
          0x00004000L, 0x00400010L, 0x20004010L, 0x00000000L, \
          0x20404000L, 0x20000000L, 0x00400010L, 0x20004010L ]

  SP7 = [ 0x00200000L, 0x04200002L, 0x04000802L, 0x00000000L, \
          0x00000800L, 0x04000802L, 0x00200802L, 0x04200800L, \
          0x04200802L, 0x00200000L, 0x00000000L, 0x04000002L, \
          0x00000002L, 0x04000000L, 0x04200002L, 0x00000802L, \
          0x04000800L, 0x00200802L, 0x00200002L, 0x04000800L, \
          0x04000002L, 0x04200000L, 0x04200800L, 0x00200002L, \
          0x04200000L, 0x00000800L, 0x00000802L, 0x04200802L, \
          0x00200800L, 0x00000002L, 0x04000000L, 0x00200800L, \
          0x04000000L, 0x00200800L, 0x00200000L, 0x04000802L, \
          0x04000802L, 0x04200002L, 0x04200002L, 0x00000002L, \
          0x00200002L, 0x04000000L, 0x04000800L, 0x00200000L, \
          0x04200800L, 0x00000802L, 0x00200802L, 0x04200800L, \
          0x00000802L, 0x04000002L, 0x04200802L, 0x04200000L, \
          0x00200800L, 0x00000000L, 0x00000002L, 0x04200802L, \
          0x00000000L, 0x00200802L, 0x04200000L, 0x00000800L, \
          0x04000002L, 0x04000800L, 0x00000800L, 0x00200002L ]

  SP8 = [ 0x10001040L, 0x00001000L, 0x00040000L, 0x10041040L, \
          0x10000000L, 0x10001040L, 0x00000040L, 0x10000000L, \
          0x00040040L, 0x10040000L, 0x10041040L, 0x00041000L, \
          0x10041000L, 0x00041040L, 0x00001000L, 0x00000040L, \
          0x10040000L, 0x10000040L, 0x10001000L, 0x00001040L, \
          0x00041000L, 0x00040040L, 0x10040040L, 0x10041000L, \
          0x00001040L, 0x00000000L, 0x00000000L, 0x10040040L, \
          0x10000040L, 0x10001000L, 0x00041040L, 0x00040000L, \
          0x00041040L, 0x00040000L, 0x10041000L, 0x00001000L, \
          0x00000040L, 0x10040040L, 0x00001000L, 0x00041040L, \
          0x10001000L, 0x00000040L, 0x10000040L, 0x10040000L, \
          0x10040040L, 0x10000000L, 0x00040000L, 0x10001040L, \
          0x00000000L, 0x10041040L, 0x00040040L, 0x10000040L, \
          0x10040000L, 0x10001000L, 0x10001040L, 0x00000000L, \
          0x10041040L, 0x00041000L, 0x00041000L, 0x00001040L, \
          0x00001040L, 0x00040040L, 0x10000000L, 0x10041000L ]

  leftt = ord(passwd[0]) << 24
  leftt |= ord(passwd[1]) << 16
  leftt |= ord(passwd[2]) << 8
  leftt |= ord(passwd[3]) << 0

  right = ord(passwd[4]) << 24
  right |= ord(passwd[5]) << 16
  right |= ord(passwd[6]) << 8
  right |= ord(passwd[7]) << 0

  work = ((leftt >> 4) ^ right) & 0x0f0f0f0fL
  right ^= work
  leftt ^= (work << 4)
  work = ((leftt >> 16) ^ right) & 0x0000ffffL
  right ^= work
  leftt ^= (work << 16)
  work = ((right >> 2) ^ leftt) & 0x33333333L
  leftt ^= work
  right ^= (work << 2)
  work = ((right >> 8) ^ leftt) & 0x00ff00ffL
  leftt ^= work
  right ^= (work << 8)
  right = ((right << 1) | ((right >> 31) & 1L)) & 0xffffffffL
  work = (leftt ^ right) & 0xaaaaaaaaL
  leftt ^= work
  right ^= work
  leftt = ((leftt << 1) | ((leftt >> 31) & 1L)) & 0xffffffffL

  for enc_round in range(0,8):
    work  = (right << 28) | (right >> 4)
    work ^= keys[enc_round*4+0]
    fval  = SP7[ work                & 0x3fL]
    fval |= SP5[(work >>  8) & 0x3fL]
    fval |= SP3[(work >> 16) & 0x3fL]
    fval |= SP1[(work >> 24) & 0x3fL]
    work  = right ^ keys[enc_round*4+1]
    fval |= SP8[ work                & 0x3fL]
    fval |= SP6[(work >>  8) & 0x3fL]
    fval |= SP4[(work >> 16) & 0x3fL]
    fval |= SP2[(work >> 24) & 0x3fL]
    leftt ^= fval
    work  = (leftt << 28) | (leftt >> 4)
    work ^= keys[enc_round*4+2]
    fval  = SP7[ work                & 0x3fL]
    fval |= SP5[(work >>  8) & 0x3fL]
    fval |= SP3[(work >> 16) & 0x3fL]
    fval |= SP1[(work >> 24) & 0x3fL]
    work  = leftt ^ keys[enc_round*4+3]
    fval |= SP8[ work                & 0x3fL]
    fval |= SP6[(work >>  8) & 0x3fL]
    fval |= SP4[(work >> 16) & 0x3fL]
    fval |= SP2[(work >> 24) & 0x3fL]
    right ^= fval

  right = ((right << 31) | (right >> 1)) & 0x0ffffffffL
  work = (leftt ^ right) & 0xaaaaaaaaL
  leftt ^= work
  right ^= work
  leftt = ((leftt << 31) | (leftt >> 1)) & 0x0ffffffffL
  work = ((leftt >> 8) ^ right) & 0x00ff00ffL
  right ^= work
  leftt ^= (work << 8)
  work = ((leftt >> 2) ^ right) & 0x33333333L
  right ^= work
  leftt ^= (work << 2)
  work = ((right >> 16) ^ leftt) & 0x0000ffffL
  leftt ^= work
  right ^= (work << 16)
  work = ((right >> 4) ^ leftt) & 0x0f0f0f0fL
  leftt ^= work
  right ^= (work << 4)

  output = chr((right>> 24) & 0x00ff)
  output += chr((right >> 16) & 0x00ff)
  output += chr((right >>  8) & 0x00ff)
  output += chr((right >>  0) & 0x00ff)
  output += chr((leftt >> 24) & 0x00ff)
  output += chr((leftt >> 16) & 0x00ff)
  output += chr((leftt >>  8) & 0x00ff)
  output += chr((leftt >>  0) & 0x00ff)

  fd = os.open(passwd_file, os.O_CREAT|os.O_APPEND|os.O_WRONLY, 0644)
  os.write(fd,output)

  s = ""
  for x in output:
      s += "%02x" % ord(x)

  return s

#=============================================================================
# Get the application information from MySQL.
#=============================================================================
def mysql_tool_info(c, appname, username):

  arr = mysql(c,"""SELECT id,vnc_geometry,vnc_depth,vnc_command,vnc_timeout,title
                   FROM %s WHERE instance='%s'""" % (mysql_tooltable,appname))
  if len(arr) != 1:
    log("Cannot find application named '%s'" % appname)
    #print "Cannot find application named '%s'<br>\n" % appname
    #sys.exit(1)
    return None
  id,geometry,depth,command,timeout,appfullname = arr[0]

  geometry = str(geometry)
  if geometry == 'None':
    log("vncGeometry missing for appname='%s' invoked by %s" % (appname,username))
    #print "vncGeometry missing for appname='%s'" % appname
    #sys.exit(1)
    return None

  # validate geometry, expecting number x number
  if re.match(GEOM_REGEXP, geometry) is None:
    log("Bad geometry for appname='%s' in app table, invoked by %s" % (appname,username))
    print "<B>Internal error.</B><BR>"
    sys.exit(1)

  try:
    depth = int(depth)
  except:
    depth = default_vnc_depth

  command = str(command)
  if command == 'None' or command == '':
    log("vncCommand missing for appname='%s' for %s" % (appname,username))
    #print "vncCommand missing for appname='%s' for %s" % (appname,username)
    #sys.exit(1)
    return None

  # validate command
  if re.match(PATH_REGEXP, command) is None:
    log("Bad command for appname='%s' in app table, invoked by %s" % (appname,username))
    print "<B>Internal error.</B><BR>"
    sys.exit(1)

  try:
    timeout = int(timeout)
  except:
    timeout = default_vnc_timeout

  appfullname = str(appfullname)
  if appfullname == 'None' or appfullname == '':
    log("Bad title for appname='%s'" % appname)
    appfullname = appname

  arr = mysql(c,"""SELECT SUM(value) FROM hosttype
                   JOIN %s
                   ON hosttype.name = %s.hostreq
                   WHERE %s.tool_version_id = %d""" % (mysql_hosttable,mysql_hosttable,mysql_hosttable,id))
  try:
    hostreq = int(arr[0][0])
  except:
    search = "SELECT SUM(value) FROM hosttype "
    first = True
    for req in default_vnc_hostreq_arr:
      if first:
        search = search + " WHERE name='%s' " % req
        first = False
      else:
        search = search + " OR name='%s' " % req
    arr = mysql(c, search)
    if len(arr) == 1:
      row=arr[0]
      hostreq=int(row[0])

  return (geometry,depth,command,hostreq,timeout,appfullname)

#=============================================================================
# Get the necessary information about an application from the DB (or LDAP).
#=============================================================================
def get_app_info(c, appname, username):
  try:
    mysql_appinfo = mysql_tool_info(c, appname, username)
  except:
    mysql_appinfo = (None,None,None,None,None,None)

  return mysql_appinfo


#=============================================================================
# Create a VNC server appropriate for a particular applications.
#=============================================================================
def create_display(c, appinfo, sess):

  (appname,geometry,depth,command,hostreq,timeout,appfullname) = appinfo

  # Generate a password for the new VNC display.
  #
  passfile = passfile_template % os.getpid()
  #log("Creating %s" % passfile)
  passwd = vncpasswd(genpasswd(8),passfile)

  # Take the lock for the display table.
  #
  mysql_lock(c,"display")

  # Find the least loaded elligible host.
  #
  arr = mysql(c,"""SELECT hostname
                   FROM host
                   WHERE provisions & %d = %d
                   AND status='up'
                   ORDER BY uses+2*rand()
                   LIMIT 1
                """ % (hostreq,hostreq))

  if len(arr) == 0:
    log("Cannot find a suitable host on which to run '%s'" % appname)
    #
    # We still have the lock, but this exit will result in a retry
    # and the subsequent invocation of create_display will benefit
    # from already having the lock.
    #
    return ("",0,"")

  row = arr[0]
  host = row[0]

  arr = mysql(c,"""SELECT dispnum
                   FROM display
                   WHERE hostname='%s' AND status='absent'
		   ORDER BY dispnum
                   LIMIT 1
                """ % host)
  if len(arr) == 1:
    row = arr[0]
    disp = row[0]
    arr = mysql(c,"""UPDATE display SET geometry='%s',depth=%d,
                     sessnum=%d,status='starting',vncpass='%s'
                     WHERE hostname='%s' AND dispnum=%d
                  """ % (geometry,depth,sess,passwd,host,disp))
  else:
    arr = mysql(c,"""SELECT MAX(dispnum) FROM display WHERE hostname='%s'
                  """ % host)
    row = arr[0]
    if row[0] == None:
      disp = 1
    else:
      disp = int(row[0]) + 1
    mysql(c,"""INSERT INTO
               display(hostname,dispnum,geometry,depth,sessnum,vncpass,status)
               VALUES('%s',%d,'%s',%d,%d,'%s','%s')
            """ % (host,disp,geometry,depth,sess,passwd,'starting'))
  mysql(c,"""UPDATE host SET uses=(
                SELECT COUNT(*) FROM display
                WHERE display.hostname=host.hostname
                AND (display.status='ready'
                     OR display.status='used'
                     OR display.status='broken'))""")
  mysql_unlock(c, "display")

  #log("creating display %d on host %s." % (disp,host))
  #log("geom = %s, depth = %d, sess = %d" % (geometry,depth,sess))

  #mysql_lock(c,"startvnc_" + host)
  status = ssh(passfile,host,"""/usr/bin/maxwell_service startvnc %d %s %d >> /var/log/mw-client/vnc-%s:%d 2>&1""" % (disp,geometry,depth,host,disp))
  #mysql_unlock(c, "startvnc_" + host)

  if sess==0:
    mysql(c,"""UPDATE display SET status='ready'
               WHERE hostname='%s' AND dispnum=%d
            """ % (host,disp))
  else:
    mysql(c,"""UPDATE display SET status='used'
               WHERE hostname='%s' AND dispnum=%d
            """ % (host,disp))

  try:
    #log("Deleting %s" % passfile)
    os.unlink(passfile)
  except OSError:
    log("Unable to delete %s" % passfile)
    pass

  if status == 0:
    #mysql(c,"""UPDATE host SET uses=uses+1 WHERE hostname='%s'""" % host)
    mysql(c,"""UPDATE host SET uses=(
                  SELECT COUNT(*) FROM display
                  WHERE display.hostname=host.hostname
                  AND (display.status='ready' OR display.status='used'))""")
    os.unlink("/var/log/mw-client/vnc-%s:%d" % (host,disp))
    return (host,disp,passwd)
  elif status == 0x6200:
    log("Display %s:%d is already running." % (host,disp))
    status = ssh("",host,"""/usr/bin/maxwell_service stopvnc %d""" % (disp))
    # If we succeed in clearing the display, just return normal failure
    # and don't mark the display as broken.
    if status == 0:
      log("Successfully shut down display %s:%d." % (host,disp))
      return ("",0,"")
  elif status == 0xff00:
    log("Unknown host %s in creating display." % host)
  else:
    log("Unknown error: 0x%x when creating display %s:%d." % (status,host,disp))

  #
  # On error, set status to 'broken'
  #
  mysql(c,"""UPDATE display SET status='broken', vncpass=''
             WHERE hostname='%s' AND dispnum=%d
          """ % (host,disp))
  return ("",0,"")

def screenshot(username):
  """This calls maxwell_service to take a screenshot for all sessions a user has
  """
  db = db_connect()
  c = db.cursor()
  rows = mysql(c,"""SELECT exechost, sessnum, dispnum FROM session WHERE username = '%s'""" % (username))
  for row in rows:
    status = ssh("",row[0],"""/usr/bin/maxwell_service screenshot %s %s%s %s""" % (username, row[1], session_suffix, row[2]))
    
    if status != 0:
      log("Screenshot failed on host '%s', display '%s' for user '%s', session '%s'" % (row[0], row[2], username, row[1]))

#============================================================================
# Destroy a VNC server.
#=============================================================================
def delete_display(c,host,disp,sessnum):

  # Check that the display is still registered as we think it is
  arr = mysql(c,"""SELECT count(*) FROM display
                   WHERE (status='used' OR status='ready')
                   AND hostname='%s' AND dispnum=%d AND sessnum=%d
                """ % (host,disp,sessnum))

  if len(arr) != 1:
    log("Bad call to delete_display(%s,%s,%s)" % (host,disp,sessnum))
    return

  if host != "":
    status = ssh("",host,"""/usr/bin/maxwell_service stopvnc %d >/dev/null 2>&1""" % disp)
    if status != 0:
      log("Delete %s:%d status was 0x%x" % (host,disp,status))

  mysql(c,"""UPDATE display SET geometry='',depth=0,
             status='absent',vncpass='',sessnum=0
             WHERE (status='used' OR status='ready')
             AND hostname='%s' AND dispnum=%d AND sessnum=%d
          """ % (host,disp,sessnum))

  mysql(c,"""UPDATE host SET uses=(
             SELECT COUNT(*) FROM display
             WHERE display.hostname=host.hostname
             AND (display.status='ready' OR display.status='used'))""")

#=============================================================================
# Create an appropriate display in the background (for later use).
#=============================================================================
def create_display_background(appinfo):

  if not background():
    return

  # Execute the following as a dissociated child.
  db = db_connect()
  c = db.cursor()
  for i in range(0,display_retries):
    (host,disp,vncpass) = create_display(c,appinfo,0)
    if host != "":
      os._exit(0)
  os._exit(0)


#=============================================================================
# Find an appropriate VNC server.
#=============================================================================
def find_display(c,appinfo,sess):
  (appname,geometry,depth,command,hostreq,timeout,appfullname) = appinfo
  #log("Finding display for %s" % geometry)
  mysql_lock(c,"display")
  arr = mysql(c,"""SELECT display.hostname,display.dispnum,display.vncpass
                   FROM display
                   JOIN host ON display.hostname=host.hostname
                   WHERE host.provisions & %d = %d
                   AND host.status='up'
                   AND display.status='ready'
                   AND display.geometry='%s'
                   AND display.depth=%d
                   ORDER BY display.dispnum LIMIT 3
                """ % (hostreq,hostreq,geometry,depth))

  if len(arr) == 0:
    mysql_unlock(c, "display")
    for i in range(0,display_retries):
      #log("None found.  Creating one.")
      (host,disp,vncpass) = create_display(c,appinfo,sess)
      if host != '':
        break
    if host == '':
      return (host,0,'')
    create_display_background(appinfo)
    return (host,disp,vncpass)

  else:
    row = arr[0]
    host=row[0]
    disp=row[1]
    vncpass=row[2]
    mysql(c,"""UPDATE display SET sessnum=%d,status='used'
                 WHERE hostname='%s' AND dispnum='%s'""" % (sess,host,disp))
    mysql_unlock(c, "display")
    if len(arr) == 1:
      create_display_background(appinfo)
    return (host,disp,vncpass)

#=============================================================================
# Stop the session
#=============================================================================
def stop_session(sess,reason):
  log("Stopping %d for reason: %s" % (sess,reason))

  db = db_connect()
  c = db.cursor()

  arr=mysql(c,"""SELECT sessnum,username,remoteip,exechost,dispnum,timeout,
                 TIME_TO_SEC(TIMEDIFF(NOW(),start)) AS walltime
                 FROM session WHERE sessnum=%d""" % sess)
  if len(arr) == 0:
    return
  else:
    row=arr[0]
    user=row[1]
    host=row[3]
    disp=row[4]
    timeout=row[5]
    walltime=row[6]

  status = 0
  if reason=='timeout':
    status = 65535
    walltime -= timeout

  err = mysql_act(c,"""
          INSERT INTO
          sessionlog(sessnum,username,remoteip,exechost,dispnum,start,appname,status,walltime)
          SELECT sessnum,username,remoteip,exechost,dispnum,start,appname,%d,%f
          FROM session WHERE sessnum=%d
          """ % (status,walltime,sess))
  if err != "":
    log("sessionlog: '%s' (somebody double-clicked close)" % err)
    return

  mysql(c,"""DELETE FROM session WHERE sessnum=%d""" % sess)
  mysql(c,"""DELETE FROM viewperm WHERE sessnum=%d""" % sess)
  mysql(c,"""DELETE FROM fileperm WHERE sessnum=%d""" % sess)

  delete_display(c,host,disp,sess)

#=============================================================================
# Stop a session in the background (no waiting).
#=============================================================================
def stop_session_background(sess,reason):
  # Execute the following as a dissociated child.
  dissociate()
  stop_session(sess,reason)
  os._exit(0)


#=============================================================================
# Invoke the command.
#=============================================================================
def invoke_command(host,disp,user,sess,comm,timeout):

  timeout = 0

  if comm.split() == []:
    log("Command is empty")
    sys.exit(1)

  # If neither "stream" nor "notify" mode is specified, force the default.
  if comm.split()[0] != "stream" and comm.split()[0] != "notify":
    comm = default_stats_mode + " " + comm

  cmd = """/usr/bin/maxwell_service startxapp %s %s%s %d %d %s </dev/null > /var/log/mw-client/sessions/%s.out 2> /var/log/mw-client/sessions/%s.err""" % (user,sess,session_suffix,timeout,disp,comm,sess,sess)
  #log("command is: %s" % cmd)
  status = ssh("",host,cmd)
  if status != 0:
    # RICK: should retry here...
    log("Exit status for session %d was %d." % (sess,status))

  if comm.split()[0] == "stream":
    try:
      process_stats(sess)
      stop_session(sess,"stream ended")
    except:
      log("Unable to open session error output file '%s'" % ("/var/log/mw-client/sessions/%s.err" % sess))
      sys.exit(1)

#=============================================================================
# Notification that the session has exited asynchronously.
#=============================================================================
def session_exit_notify(sess):
  status = get_stats(sess)
  if status != 0:
    status = process_stats(sess)
    return 0  # no unix error
  else:
    return 1  # unix error

#=============================================================================
# Get session stats after asynchronous notifier.
#=============================================================================
def get_stats(sess):

  db = db_connect()
  c = db.cursor()

  #
  # If the session was terminated by the user, stop_session has already been
  # called and the session table doesn't have this entry anymore.
  # Otherwise we can get it from session.  Either source should have the
  # same exechost.
  #
  arr = mysql(c,"""SELECT exechost FROM session WHERE sessnum=%d""" % sess)
  if len(arr) == 0:
    arr = mysql(c,"""SELECT exechost FROM sessionlog WHERE sessnum=%d""" % sess)
    if len(arr) == 0:
      log( "Unknown session '%s'" % sess)
      return 0

  row=arr[0]
  exechost = row[0]

  for i in range(0,get_stats_retries):
    status = scp(exechost, """root@%s:/var/log/mw-service/open-sessions/%s.\* /var/log/mw-client/sessions""" % (exechost,sess))
    if status != 0:
      log( """'scp root@%s:/var/log/mw-service/open-sessions/%s.\* /var/log/mw-client/sessions' failed with status %s""" % (exechost,sess,status))
      time.sleep(get_stats_delay)
    else:
      for i in range(0,get_stats_retries):
        cmd = """/usr/bin/maxwell_service purgeoutputs %s""" % sess
        status = ssh("",exechost,cmd)
        if status != 0:
          log("Purgelogs status for %s was %s." % (sess,status))
          time.sleep(get_stats_delay)
        else:
          # success...
          return 1
      break

  # Something must have ran out of retries...
  return 0

#=============================================================================
# Parse a time specification for a subtask.
#=============================================================================
def parse_time(sessnum,line,db):
  #log("parse_time")
  c = db.cursor()

  event=""
  job=0
  start=0.0
  wall=0.0
  cpu=0.0
  ncpus=1
  venue=""
  status=0

  for item in line.split():
    try:
      if item=='MiddlewareTime:':
        continue

      (k,v) = item.split('=')
      #log("parsing %s=%s" % (k,v))

      if k=='job':
        job=int(v)
      elif k=='event':
        event=v
      elif k=='start':
        start=float(v)
      elif k=='walltime':
        wall=float(v)
      elif k=='cputime':
        cpu=float(v)
      elif k=='ncpus':
        ncpus=int(v)
      elif k=='venue':
        venue=v
      elif k=='status':
        status=int(v)
      else:
        log("Unknown tag in parse_time: %s" % k)
      continue

    except:
      log("Bad item in parse_time: %s in %s" % (item,line))
      return

  if sessnum == 0 or job == 0 or event=='':
    log("parse_time: bad format: %s" % line)
    return

  mysql(c,"""
          INSERT INTO
          joblog(sessnum,job,event,start,walltime,cputime,ncpus,status,venue)
          SELECT sessnum,%d, "%s", date_add(start,interval %f second_microsecond),
                                         %f,      %f,     %d,   %d,    "%s"
          FROM sessionlog WHERE sessnum=%d
          """ % (job,event,start,wall,    cpu,    ncpus,status,venue,sessnum))

#=============================================================================
# Sum the view time for a session.
#=============================================================================
def sum_viewtime(c,sessnum):
  debug_viewtime = 0
  views_remaining = 1
  printed_notice = 0
  wait_total = 0
  while views_remaining:
    arr = mysql(c,"""SELECT COUNT(*) FROM view WHERE sessnum=%d""" % sessnum)
    row = arr[0]
    views_remaining = row[0]
    if views_remaining > 0:
      if printed_notice == 0:
        log("Continue %d checking remaining view." % sessnum)
        printed_notice = 1
      time.sleep(5)
      wait_total += 5

  if printed_notice:
    log("WaitedOn %d a total of %d seconds for remaining view." % (sessnum,wait_total))

  arr = mysql(c,"""SELECT unix_timestamp(time),duration,remoteip
                   FROM viewlog WHERE sessnum=%d ORDER BY time""" % sessnum)

  sum=0
  prevfinish=0
  for row in arr:
    start=int(row[0])
    duration=float(row[1])
    finish=start+duration
    remoteip=row[2]

    if duration <= 0:
      continue

    if prevfinish < start:
      prevfinish = finish
      sum += duration
    else:
      if finish < prevfinish:
         if debug_viewtime:
           log("Skipping next record because it is contained in previous.")
      else:
        overlap = prevfinish - start
	if debug_viewtime:
          log("Next record overlaps with previous by %f" % overlap)
        tmp_duration = duration - overlap
        sum += tmp_duration
        prevfinish = finish

    if debug_viewtime:
      log("%d: %f %f %s" % (start,duration,finish,remoteip))

  return sum

#=============================================================================
# Get the statistics from the command.
#=============================================================================
def process_stats(sess):

  # This may be redundant.  Do it to make sure sessionlog entry exists.
  stop_session(sess,"exited")

  try:
    file=open("/var/log/mw-client/sessions/%s.err" % sess)
  except:
    log("Unable to open /var/log/mw-client/sessions/%s.err" % sess)
    return

  db = db_connect()
  c = db.cursor()

  #
  # Get fields that are already in the session log...
  #
  rows = mysql(c,"""SELECT walltime,status FROM sessionlog
                    WHERE sessnum=%d""" % sess);
  arr = rows[0]
  rtime = float(arr[0])
  status = int(arr[1])

  utime=0.0
  stime=0.0
  vtime=0.0
  reported_timeout=0
  while 1:
    line = file.readline()
    if line=='':
      break;

    if line.startswith("MiddlewareTime: "):
      parse_time(sess,line,db)
      continue

    if line.startswith("Timeout_Value: "):
      #log("Timeout status for %s: %s" % (sess,line))
      try:
        reported_timeout=int(line.split(' ')[1])
        #log("Timeout is %d" % reported_timeout)
      except:
        continue

    if status == 0:
      if line.startswith("Exit_Status: "):
        #log("Exit status for %s: %s" % (sess,line))
        try:
          status=int(line.split(' ')[1])
          #log("Status is %d" % status)
        except:
          continue

    #if line.startswith("view "):
    #  try:
    #    vtime=float(line.split(' ')[1])
    #  except:
    #    vtime=0
    #    continue

    if status != 65535:
      if line.startswith("real "):
        try:
          rtime=float(line.split(' ')[1])
        except:
          rtime=0
          continue

    if line.startswith("user "):
      try:
        utime=float(line.split(' ')[1])
      except:
        utime=0
        continue

    if line.startswith("sys "):
      try:
        stime=float(line.split(' ')[1])
      except:
        stime=0
        continue

  # If session timed out, subtract the timeout value from the
  # wall time.  This gives a realistic value for how long the
  # user was actually looking at the application.
  #
  # Also set the status value to something that indicates timeout.
  # 65535 is an impossible exit value.
  if reported_timeout > 0:
    rtime -= reported_timeout
    status = 65535
    
  #log( "Session %s status was 0x%x" % (sess,status))
  #log( "Real Time was %f" % rtime)
  #log( "User Time was %f" % utime)
  #log( "Sys  Time was %f" % stime)
  #log( "View Time was %f" % vtime)

  strange=0
  if rtime < 0:
    log("Session %d rtime(%f) < 0" % (sess,rtime))
    rtime=0
    strange=1

  if vtime < 0:
    log("Session %d vtime(%f) < 0" % (sess,vtime))
    vtime=0
    strange=1

  if utime+stime < 0.01:
    log("ERROR: Session %d consumed no CPU." % sess)
    strange=1

  vtime = sum_viewtime(c,sess)

  mysql(c,"""
          UPDATE sessionlog
          SET walltime=%f,viewtime=%f,cputime=%f,status=%d
          WHERE sessnum=%d
          """ % (rtime, vtime, utime+stime, status, sess))

  mysql(c,"""
          INSERT INTO
          joblog(sessnum,job,event,start,walltime,cputime,ncpus,status,venue)
          SELECT sessnum,%d, "%s", start,%f,      %f,     1,    %d,    "%s"
          FROM sessionlog WHERE sessnum=%d
          """ % (0, "application", rtime, utime+stime, status, "", sess))

  #if not strange and (status == 0 or reported_timeout > 0):
  #  os.unlink("/var/log/mw-client/sessions/%d.out" % sess)
  #  os.unlink("/var/log/mw-client/sessions/%d.err" % sess)

  db.close()

#=============================================================================
# Print the HTML code to load the applet.
#=============================================================================
def view_applet(c,sess,user,ip,force_ro):

  rows = mysql(c,"""SELECT viewuser,geometry,fwhost,fwport,vncpass,readonly,viewtoken,appname
                    FROM viewperm JOIN session ON viewperm.sessnum = session.sessnum
                    WHERE session.sessnum=%d""" % sess);

  if len(rows) == 0:
    print "Unable to view application.<br>"
    return 0

  row=0
  for r in rows:
    if r[0] == user:
      row = r
      break

  if row==0:
    print "No such session"
    return 0

  geometry = row[1]
  fwhost = row[2]
  fwport = row[3]
  vncpass = row[4]
  readonly = row[5]
  viewtoken = row[6]
  appname = row[7]
  (width,height) = geometry.split('x')
  debug = False

  if force_ro:
    readonly = 'Yes'

  if json_results:
    appvars = {}
    appvars["debug"] = debug
    appvars["connect"] = """vncsession:%s""" % viewtoken
    appvars["encoding"] = "ZRLE"
    appvars["width"] = int(width)
    appvars["height"] = int(height)
    appvars["encpassword"] = vncpass
    appvars["port"] = vnc_proxy_port
    appvars["show_controls"] = False
    appvars["show_local_cursor"] = False
    appvars["readonly"] = (force_ro == True)
    appvars['wsproxy_port'] = wsproxy_port
    appvars['wsproxy_host'] = wsproxy_host
    appvars['wsproxy_encrypt'] = wsproxy_encrypt
    print json.dumps(appvars)

  else:
    print """
	<div id="app-wrap">
		<applet id="theapp" code="VncViewer.class"
			archive="%s"
			width="%d" height="%d" MAYSCRIPT>
			<param name="PORT" value=%d>
			<param name="ENCPASSWORD" value="%s">
			<param name="CONNECT" value="vncsession:%s">
			<param name="View Only" value="%s">
			<param name="trustAllVncCerts" value="Yes">
			<param name="Offer relogin" value="Yes">
			<param name="DisableSSL" value="No">
			<param name="Show controls" value="No">
			<param name="ENCODING" value="ZRLE">
	</div>
    """ % ("/components/com_tools/"+applet,int(width),int(height)+0,vnc_proxy_port,vncpass,viewtoken,readonly)

  #
  # Set up file browser applet
  #
  rows = mysql(c,"""SELECT fwhost,fwport,cookie
                    FROM fileperm WHERE sessnum=%d
                    LIMIT 1""" % sess);

  if len(rows) == 0:
    print "Unable to use filebrowser.<br>"
    return 0

  row = rows[0]

  fwhost = row[0]
  fwport = row[1]
  cookie = row[2]

  return 0

#=============================================================================
# Find the fileserver.
#=============================================================================
def find_fileserver(c):
  arr = mysql(c,"""SELECT host.hostname FROM host
                   JOIN hosttype ON host.provisions & hosttype.value != 0
                   WHERE hosttype.name='fileserver'""")
  if len(arr) != 1:
    log("Cannot find a single host with a 'fileserver' attribute set.")
    sys.exit(1)
  else:
    row = arr[0]
    fileserver = row[0]
    return fileserver

#=============================================================================
# Set up the account on the fileserver.
#=============================================================================
def setup_account(c,fileserver,user,sess,params):

  for iter in range(0,homedir_retries):
    mysql_lock(c, "homedir_" + fileserver)
    status = ssh("",fileserver,"/usr/bin/maxwell_service setup_dir %s %d%s %s" % (user,sess,session_suffix,params))
    mysql_unlock(c, "homedir_" + fileserver)
    if status == 0:
      break

    print "Unable to set up the home directory.<br>\n"
    mysql(c,"""DELETE FROM session WHERE sessnum=%d""" % sess)
    sys.exit(1)

#=============================================================================
# Create home directory on the fileserver.
#=============================================================================
def create_userhome(c,user):

  fileserver = find_fileserver(c)

  for iter in range(0,homedir_retries):
    mysql_lock(c, "homedir_" + fileserver)
    status = ssh("",fileserver,"/usr/bin/maxwell_service create_userhome %s" % (user))
    mysql_unlock(c, "homedir_" + fileserver)
    if status == 0:
      break

    print "Unable to set up the home directory.<br>\n"
    sys.exit(1)

#=============================================================================
# Change the quota for a user on the fileserver
#=============================================================================
def update_quota(c,user,block_soft,block_hard):
    
  fileserver = find_fileserver(c)
  
  for iter in range(0,homedir_retries):
    mysql_lock(c, "homedir_" + fileserver)
    status = ssh("",fileserver,"/usr/bin/maxwell_service update_quota %s %d %d" % (user, block_soft, block_hard))
    mysql_unlock(c, "homedir_" + fileserver)
    if status == 0:
      break

    print "Unable to set up the home directory quotas.<br>\n"
    sys.exit(1)

#=============================================================================
# Append stuff to the session resource file.
#=============================================================================
def update_resources(c,fileserver,user,sess,disp,appinfo,sesstoken,version):

  (appname,geometry,depth,command,hostreq,timeout,appfullname) = appinfo

  do_submit=0
  for sa in submit_app_list.keys():
    if appname.startswith(sa):
      do_submit=1

  fileport = disp + 9000
  alpha = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
  filecookie = genpasswd(16,alpha)

  for iter in range(0,homedir_retries):
    mysql_lock(c, "resources_" + fileserver)

    try:
      check_ssh_host_key(fileserver)
      comm = "/usr/bin/maxwell_service update_resources %s %d%s" % (user,sess,session_suffix)
      pipe = os.popen("ssh -i /etc/mw-client/maxwell.key root@%s %s" % (fileserver,comm), "w")
    except IOError:
      print "Unable to open pipe in update_resources.<br>"
      log( "Unable to open pipe in update_resources.")
      continue

    pipe.write("filexfer_port %d\n" % fileport)
    pipe.write("filexfer_cookie %s\n" % filecookie)
    pipe.write(filexfer_decoration)
    pipe.write("\n")
    pipe.write(visualization_params)
    pipe.write("\n")
    pipe.write("""application_name "%s"\n""" % appfullname)
    pipe.write("""version %s\n""" % version)
    pipe.write("""session_token %s\n""" % sesstoken)
    pipe.write("""hub_name %s\n""" % hub_name)
    pipe.write("""hub_url %s\n""" % hub_url)
    if hub_template != "":
        pipe.write("""hub_template %s\n""" % hub_template)
    if do_submit:
      pipe.write("""job_protocol submit\n""")
    else:
      pipe.write("""job_protocol exec\n""")
    pipe.close()
    break

  mysql_unlock(c, "resources_" + fileserver)

  return filecookie

#=============================================================================
# Do everything to create the session.
#=============================================================================
def create_session(user,ip,appname,timeout,version,appopts="",params="",zone=""):

  db = db_connect()
  c = db.cursor()

  (geometry,depth,command,hostreq,new_timeout,appfullname) = get_app_info(c,appname,user)
  if timeout == -1:
    timeout = new_timeout

  mysql(c,"""INSERT INTO
             session(sessnum,username,remoteip,exechost,dispnum,start,accesstime,appname,sessname,sesstoken,timeout)
             VALUES(null,    '%s',    '%s',    '',      0,      now(),now(),     '%s',   "%s",    md5(rand()),%d)
          """ % (user,ip,appname,appfullname,timeout))
  arr = mysql(c,"""SELECT sessnum,sesstoken FROM session
                   WHERE sessnum=last_insert_id()""")
  row = arr[0]
  sess = int(row[0])
  sesstoken = row[1]

  fileserver = find_fileserver(c)

  # Set up the user's home directory and session information.
  setup_account(c,fileserver,user,sess,params)

  # Find a suitable VNC display.
  appinfo = (appname,geometry,depth,command,hostreq,new_timeout,appfullname)
  (host,disp,vncpass) = find_display(c,appinfo,sess)
  if host=="":
    print """Unable to create a display for '%s'.
             This probably means that the application has not been set up.
          """ % appname
    log("Unable to create a display for '%s'.  Session %d" % (appname,sess))
    mysql(c,"""DELETE FROM session WHERE sessnum=%d""" % sess)
    sys.exit(1)

  log("Starting %d (%s) for %s on %s:%d" % (sess,appname,user,host,disp))

  mysql(c,"""UPDATE session SET exechost='%s',dispnum=%d
             WHERE sessnum=%d """ % (host,disp,sess))

  # Update the resources file.
  cookie = update_resources(c,fileserver,user,sess,disp,appinfo,sesstoken,version)

  mysql(c,"""INSERT INTO
             viewperm(sessnum,viewuser,viewtoken,geometry,vncpass,readonly)
             VALUES(%d,      '%s',     md5(rand()),'%s',  '%s',   '%s')
          """ % (sess,user,geometry,vncpass,'No'))

  mysql(c,"""INSERT INTO
             fileperm(sessnum,fileuser,cookie)
             VALUES(%d,'%s','%s')
          """ % (sess,user,cookie))

  # Session info and applet is displayed below...
  #
  print "Session is", sess, "<br>\n"
  if not os.isatty(1):
    view_applet(c,sess,user,ip,0);

  db.close()

  # Execute the following as a dissociated child.
  dissociate()
  invoke_command(host,disp,user,sess,command,timeout,params)
  os._exit(0)


def parse_options(opts,args):
  for arg in args:
    try:
      (k,v) = arg.split('=')
      if opts.has_key(k):
        opts[k] = v
      else:
        log("Unrecognized argument: %s" % str(arg))
        return 0
    except ValueError:
      return 0
    except:
      log("Exception in parse_options: " + str(sys.exc_info()[0]))
      return 0
  return 1

#=============================================================================
#=============================================================================
# Main program...
#
# We recognize five distinct commands:
#   start <user> <ip> <appname> <timeout>
#   stop <sessionid>
#   view <sessionid>
#
#   check <hostname> <confirm>
#   notify session <sessionnum>
#=============================================================================
#=============================================================================

openlog(master_logfile_name)

login =  pwd.getpwuid(os.geteuid())[0]
if login != mw_user:
  print "maxwell: access denied to %s. Must be run as %s (see /etc/mw-client/maxwell.conf)" % (login, mw_user)
  os._exit(100)

os.environ["HOME"]=web_homedir

if len(sys.argv) < 2:
  log("Incomplete command: %s" % str(sys.argv))
  sys.exit(1)

log("command: %s" % str(sys.argv[1]))

if sys.argv[1] == "start":
  opts={ 'user':None,
         'ip':None,
         'app':None,
         'timeout':-1,
         'version':default_version,
         'params':"",
         'zone':"" }

  if parse_options(opts, sys.argv[2:]) == 0:
    if len(sys.argv) < 5:
      log("Syntax error: %s" % str(sys.argv))
      sys.exit(1)
    else:
      opts['user'] = sys.argv[2]
      opts['ip']   = sys.argv[3]
      opts['app']  = sys.argv[4]

  if opts['version'] == 'default':
    opts['version'] = default_version

  sys.exit(create_session(opts['user'],
                          opts['ip'],
                          opts['app'],
                          int(opts['timeout']),
                          opts['version'],
                          '',
                          opts['params'],
                          opts['zone']))

elif sys.argv[1] == "stop":
  opts = { 'sessnum':None,
           'reason':'user' }

  if parse_options(opts,sys.argv[2:]) == 0:
    if len(sys.argv) != 3:
      log("Syntax error: %s" % str(sys.argv))
      sys.exit(1)
    opts['sessnum'] = sys.argv[2]

  sess = int(opts['sessnum'])
  os._exit(stop_session_background(sess,opts['reason']))

elif sys.argv[1] == "view":
  opts = { 'user':None,
           'ip':None,
           'sess':None,
           'readonly':0 }

  if parse_options(opts,sys.argv[2:]) == 0:
    if len(sys.argv) != 5:
      log("Syntax error: %s" % str(sys.argv))
      sys.exit(1)
    opts['user'] = sys.argv[2]
    opts['ip']   = sys.argv[3]
    opts['sess'] = sys.argv[4]

  db = db_connect()
  c = db.cursor()
  sys.exit(view_applet(c,int(opts['sess']),
                         opts['user'],
                         opts['ip'],
                         opts['readonly']))

elif sys.argv[1] == "check":
  if len(sys.argv) != 4:
    log("Syntax error: %s" % str(sys.argv))
    sys.exit(1)

  sys.exit(check_host(sys.argv[2],sys.argv[3]))

elif sys.argv[1] == "notify":
  fromhost=""
  try:
    conn = os.environ["SSH_CONNECTION"]
    fromhost = conn.split()[0]
  except:
    pass

  try:
    cmd = os.environ["SSH_ORIGINAL_COMMAND"]
    if len(cmd.split()) != 3 or cmd.split()[0] != "notify" or cmd.split()[1] != "session":
      log("Bad notify command sent from %s: %s" % (fromhost,cmd))
      sys.exit(1)
    else:
      try:
        sessname=cmd.split()[2]
        sessnum=int(sessname)
      except TypeError:
        log("Notify session '%s' is not an integer (host: %s)." % (sessname, fromhost))
        sys.exit(1)
      log("Notified %d from %s" % (sessnum,fromhost))
      sys.exit(session_exit_notify(sessnum))
  except KeyError:
    if len(sys.argv) != 3:
      log("Syntax error: %s" % str(sys.argv))
      sys.exit(1)
    sess = int(sys.argv[2])
    sys.exit(session_exit_notify(sess))

elif sys.argv[1] == "screenshot":
  if len(sys.argv) != 3:
    log("Syntax error: %s" % str(sys.argv))
    sys.exit(1)

  sys.exit(screenshot(sys.argv[2]))
 
elif sys.argv[1] == "create_userhome":
  if len(sys.argv) != 3:
    log("Syntax error: %s" % str(sys.argv))
    sys.exit(1)

  db = db_connect()
  c = db.cursor()
  sys.exit(create_userhome(c,sys.argv[2]))

elif sys.argv[1] == "update_quota":
  if len(sys.argv) != 5:
    log("Syntax error: %s" % str(sys.argv))
    sys.exit(1)

  db = db_connect()
  c = db.cursor()
  sys.exit( update_quota(c,sys.argv[2],int(sys.argv[3]),int(sys.argv[4])) )

else:
  log("Unknown command: %s" % str(sys.argv))
  sys.exit(1)
