#!/usr/bin/python
#
# Copyright 2006-2009 by Purdue Research Foundation, West Lafayette, IN 47906.
# All rights reserved.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License, version 2.1 as published by the Free Software Foundation.

import DNS
import MySQLdb
import os
import sys
import signal
import time
import pty
import ldap
import md5
import pwd
import re

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

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

logfile = None
master_logfile_name = "/var/log/hubzero/master"

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

submit_app_list = {}

#=============================================================================
# Load default parameters...
#=============================================================================
dns_retries=10
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
vncpasswd_retries=10
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=900
default_vnc_depth=24
default_vnc_hostreq_arr=['sessions']
default_version='current'
ldap_host_list=""
ldap_user=""
ldap_password=""
ldap_basedn=""
ldap_query_string="tool=%s"
vnc_proxy_port=8080
mw_user="www-data"
hub_name=""
hub_url=""
hub_homedir=""

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

if hub_name == 'nanohub':
  submit_app_list["workspace"] = 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/hubzero/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/hubzero/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/hubzero/notify.key.pub", os.O_RDONLY)
  line = os.read(pubkey, 10000)
  line = """COMMAND="/usr/lib/hubzero/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/hubzero/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/hubzero/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/hubzero/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/hubzero/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/hubzero/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/hubzero/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/hubzero/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/hubzero/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/lib/hubzero/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/hubzero/notify.key root@%s:/etc/hubzero/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 an n-byte random string.
#=============================================================================
def urandom(n):
  try:
    _urandomfd = os.open("/dev/urandom", os.O_RDONLY)
  except:
    raise os.NotImplementedError("/dev/urandom (or equivalent) not found")
  bytes = ""
  while len(bytes) < n:
    bytes += os.read(_urandomfd, n-len(bytes))
  os.close(_urandomfd)
  return bytes

#=============================================================================
# Generate a cleartext password.
#=============================================================================
def genpasswd(length,letters=None):
  if letters==None:
    letters="""abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_+=~;:'",.<>?/!@#$%^&*()[]{} """
  r = 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)

#=============================================================================
# DNS test
#=============================================================================
def dns_test():
  DNS.ParseResolvConf()
  r=DNS.Request(qtype='A')
  res = r.req('www.google.com')
  for ans in res.answers:
    if ans['typename'] == 'A':
      print ans['data']

#dns_test()
#sys.exit(0)

#=============================================================================
# Do DNS name->addr resolution.
#=============================================================================

def dns_res2(name):
  DNS.ParseResolvConf()
  r=DNS.Request(qtype='A')
  res = r.req(name)
  for ans in res.answers:
    if ans['typename'] == 'A':
      return ans['data']
  return '0.0.0.0'

def dns_resolve(name,search_domains):
  #print "Searching for '%s'<br>" % name
  for n in range(0,dns_retries):
    try:
      addr = dns_res2(name)
      if addr != "0.0.0.0":
        return addr
      for domain in search_domains:
        addr = dns_res2(name + "." + domain)
        if addr != "0.0.0.0":
          return addr
    except:
      pass
  print "<b>There is a local problem with domain name service.</b><br>"
  log("Problem with domain name service.")
  return '0.0.0.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):
  (pid, fd) = os.forkpty()

  if pid < 0:
    return 0
  elif pid == 0:
    os.putenv('HOME','/tmp') # vncpasswd wants HOME to be defined
    os.execlp("vncpasswd", "vncpasswd", passwd_file)
    log("Unable to execlp(vncpasswd)")
    return ""

  os.read(fd,100)               # read the prompt
  os.write(fd, passwd + "\n")   # write the password
  os.read(fd,100)               # read challenge
  os.read(fd,100)               # read newline
  os.write(fd, passwd + "\n")   # write the password
  os.read(fd,100)               # read junk
  os.write(fd, "n\n")           # don't write view password
  os.read(fd,100)               # read junk
  try:
    (pid, status) = os.waitpid(pid,0)
  except OSError, (errno, msg):
    print __name__, "waitpid:", msg
    return ""

  os.close(fd)
  fd = os.open(passwd_file, os.O_RDONLY)
  bytes = os.read(fd,8)
  os.close(fd)
  #os.unlink(passwd_file)

  enc = ""
  for i in range(0,8):
    enc += "%02x" % ord(bytes[i])
  return enc

#for i in range(0,1000):
#  p = vncpasswd(genpasswd(8))
#  if p == '':
#    print "Bad password"
#    sys.exit(1)
#  print p
#sys.exit(0)

#=============================================================================
# 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 application information from LDAP.
#=============================================================================
def ldap_tool_info_query(appname):
  ldapresult=[]

  # Check the following note for more info on ldaps...
  # http://sourceforge.net/mailarchive/forum.php?forum_id=4346&max_rows=25&style=flat&viewmonth=200508
  for ldap_host in ldap_host_list.split():
    try:
      if ldap_host.startswith("ldaps://"):
        ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT,ldap.OPT_X_TLS_ALLOW)
        l = ldap.initialize(ldap_host)
      elif ldap_host.startswith("ldap://"):
        l = ldap.initialize(ldap_host)
      else:
        l = ldap.open(ldap_host)
    except ldap.LDAPError, msg:
      log("%s: %s" % (ldap_host,msg))
      continue

    try:
      status = l.simple_bind(ldap_user, ldap_password)
      if status != 1:
        log("Unable to bind to LDAP server")
        sys.exit(1)
    except ldap.LDAPError, msg:
      log("%s: %s" % (ldap_host,msg))
      continue

    try:
      ldapresult = l.search_s(ldap_basedn, ldap.SCOPE_SUBTREE,
                              ldap_query_string % appname)
      l.unbind()
      return ldapresult
    except ldap.LDAPError, msg:
      log("%s: %s" % (ldap_host,msg))
      l.unbind()
      return []

  log("Unable to connect to any LDAP server")
  print "Unable to contact application directory server.<br>\n"
  return []

#=============================================================================
# Get the necessary information about an application from LDAP.
#=============================================================================
def ldap_tool_info(c, appname, username):

  ldapresult = ldap_tool_info(appname)
  if len(ldapresult) == 1:
    record = ldapresult[0]
    params = record[1]

    try:
      geometry = params['vncGeometry'][0]
    except KeyError:
      log("vncGeometry missing for appname='%s' invoked by %s" % (appname,username))
      print "vncGeometry missing for appname='%s'" % appname
      sys.exit(1)

    try:
      depth = int(params['vncDepth'][0])
    except KeyError:
      depth = default_vnc_depth
    except ValueError:
      log("Bad depth value for appname='%s'" % appname)

    try:
      command = params['vncCommand'][0]
    except KeyError:
      log("vncCommand missing for appname='%s'" % appname)
      sys.exit(1)

    try:
      timeout = int(params['vncTimeout'][0])
    except KeyError:
      timeout = default_vnc_timeout
    except ValueError:
      log("Bad timeout value for appname='%s'" % appname)

    try:
      appfullname = params['cn'][0]
    except KeyError:
      appfullname = appname
    except ValueError:
      log("Bad cn value for appname='%s'" % appname)

    try:
      hostreq_arr = params['vncHostReq']
    except KeyError:
      hostreq_arr = default_vnc_hostreq_arr

    search = "SELECT SUM(value) FROM hosttype "
    first = True
    for req in 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)

    else:
      log("Problem finding hosttype for '%s'" % search)
      sys.exit(1)

  log("Cannot find application named '%s'" % appname)
  print "Cannot find application named '%s'<br>\n" % appname
  sys.exit(1)

#=============================================================================
# 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)

  #try:
  #  ldap_appinfo = ldap_tool_info(c, appname, username)
  #except:
  #  ldap_appinfo = (None,None,None,None,None,None)
  #if mysql_appinfo != ldap_appinfo:
  #  log("APPINFO DOES NOT MATCH:\n%s\n%s" % (str(mysql_appinfo),str(ldap_appinfo)))

  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.
  #
  for x in range(0,vncpasswd_retries):
    passfile = passfile_template % os.getpid()
    #log("Creating %s" % passfile)
    passwd = vncpasswd(genpasswd(8),passfile)
    if passwd != "":
      break

  # 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/lib/hubzero/bin/maxwell_service startvnc %d %s %d >> /var/log/hubzero/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/hubzero/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/lib/hubzero/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,"")

#=============================================================================
# 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/lib/hubzero/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/lib/hubzero/bin/maxwell_service startxapp %s %s%s %d %d %s </dev/null > /var/log/hubzero/sessions/%s.out 2> /var/log/hubzero/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/hubzero/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/lib/hubzero/%s.\* /var/log/hubzero/sessions""" % (exechost,sess))
    if status != 0:
      log( """'scp root@%s:/var/lib/hubzero/%s.\* /var/log/hubzero/sessions' failed with status %s""" % (exechost,sess,status))
      time.sleep(get_stats_delay)
    else:
      for i in range(0,get_stats_retries):
        cmd = """/usr/lib/hubzero/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/hubzero/sessions/%s.err" % sess)
  except:
    log("Unable to open /var/log/hubzero/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/hubzero/sessions/%d.out" % sess)
  #  os.unlink("/var/log/hubzero/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')

  if force_ro:
    readonly = 'Yes'

  if 1: 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">
		<!--
			<param name="Open new window" value="Yes">
			<param name="Show offline desktop" value="No">
			<param name="Cursor shape updates" value="Disable">
		-->
			<p class="error">
			In order to view an application,
			you must have Java installed and enabled.
			(<a href="/support/java/">How do I do this?</a>)
			</p>
		</applet >
	</div>
  """ % ("/components/com_tools/"+applet,int(width),int(height)+0,vnc_proxy_port,vncpass,viewtoken,readonly)
  else: print """
	<div id="app-wrap">
		<div id="theapp"> </div>
	</div>
  """

  if 1: print """
	<script type="text/javascript">
	function addParam(obj,name,value) {
		var p = document.createElement("param");
		p.name = name;
		p.value = value;
		obj.appendChild(p);
	}

	function loadApplet_MSIE(jar,w,h,port,pass,token,ro) {
		var app=document.getElementById("theapp");
		var par=app.parentNode;
		par.removeChild(app);
		var newapp=document.createElement("applet");
		newapp.id = "theapp";
		newapp.code = "VncViewer.class";
		newapp.archive = jar;
		newapp.width = w;
		newapp.height = h;
		newapp.mayscript = "mayscript";

		addParam(newapp, "PORT", port);
		addParam(newapp, "ENCPASSWORD", pass);
		addParam(newapp, "CONNECT", "vncsession:" + token);
		addParam(newapp, "ENCODING", "ZRLE");
		addParam(newapp, "View Only", ro);
		addParam(newapp, "trustAllVncCerts", "Yes");
		addParam(newapp, "Offer relogin", "Yes");
		addParam(newapp, "DisableSSL", "No");
		addParam(newapp, "Show controls", "No");

		//addParam(newapp, "Open new window", "Yes");
		//addParam(newapp, "Show offline desktop", "No");
		//addParam(newapp, "Cursor shape updates", "Disable");

                if (jar.indexOf('Signed') >= 0) {
			addParam(newapp, "signed", "yes");
			addParam(newapp, "forceProxy", "yes");
		}

		par.appendChild(newapp);
	}

	function loadApplet_normal(jar,w,h,port,pass,token,ro)
	{
		var app=document.getElementById("theapp");
		var par = app.parentNode;
		par.removeChild(app);
		var appletDiv = document.createElement("div");
		var signed;
                if (jar.indexOf('Signed') >= 0) {
			signed = 'Yes';
		} else {
			signed = 'No';
		}
		content = '<applet id="theapp" code="VncViewer.class" ' +
			'archive="%JAR%" ' +
			'width="%W%" height="%H%" MAYSCRIPT> ' +
			'<param name="PORT" value=%PORT%> ' +
			'<param name="ENCPASSWORD" value="%PASS%"> ' +
			'<param name="CONNECT" value="vncsession:%TOKEN%"> ' +
			'<param name="View Only" value="%RO%"> ' +
			'<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"> ' +
		'<!-- ' +
			'<param name="Open new window" value="Yes"> ' +
			'<param name="Show offline desktop" value="No"> ' +
			'<param name="Cursor shape updates" value="Disable"> ' +
		'--> ' +
			'<param name="signed" value="%SIGNED%"> ' +
			'<param name="forceProxy" value="%SIGNED%"> ' +
		'</applet >';
		content=content.replace("%JAR%",jar);
		content=content.replace("%W%",w);
		content=content.replace("%H%",h);
		content=content.replace("%PORT%",port);
		content=content.replace("%PASS%",pass);
		content=content.replace("%TOKEN%",token);
		content=content.replace("%RO%",ro);
		content=content.replace("%SIGNED%",signed);
		content=content.replace("%SIGNED%",signed);
		appletDiv.innerHTML = content;
		par.appendChild(appletDiv);
	}
	</script>
  """

  if 1: print """
	<script type="text/javascript">
	function loadUnsignedApplet()
	{
		var jar="%s";
		var w=%d;
		var h=%d;
		var port=%d;
		var pass="%s";
		var token="%s";
		var ro="%s";
		var ua;
		ua = navigator.userAgent;
		if ((ua.indexOf('MSIE') >= 0) || (ua.indexOf('xxOther') >= 0)) {
			loadApplet_MSIE(jar,w,h,port,pass,token,ro);
		} else {
			loadApplet_normal(jar,w,h,port,pass,token,ro);
		}
	}
	</script>
  """ % ("/components/com_tools/"+applet,int(width),int(height),vnc_proxy_port,vncpass,viewtoken,readonly)

  if 1: print """
	<script type="text/javascript">
	function loadSignedApplet()
	{
		var jar="%s";
		var w=%d;
		var h=%d;
		var port=%d;
		var pass="%s";
		var token="%s";
		var ro="%s";
		var ua;
		ua = navigator.userAgent;
		if ((ua.indexOf('MSIE') >= 0) || (ua.indexOf('xxOther') >= 0)) {
			loadApplet_MSIE(jar,w,h,port,pass,token,ro);
		} else {
			loadApplet_normal(jar,w,h,port,pass,token,ro);
		}
	}
	</script>
  """ % ("/components/com_tools/"+signedapplet,int(width),int(height)+0,vnc_proxy_port,vncpass,viewtoken,readonly)

  if 1: print """
	<script type="text/javascript">
	startAppletTimeout();
	connectingTool();
	</script>
  """

  #
  # 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):

  for iter in range(0,homedir_retries):
    mysql_lock(c, "homedir_" + fileserver)
    status = ssh("",fileserver,"/usr/lib/hubzero/bin/maxwell_service setup_dir %s %d%s" % (user,sess,session_suffix))
    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)

#=============================================================================
# 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/lib/hubzero/bin/maxwell_service update_resources %s %d%s" % (user,sess,session_suffix)
      pipe = os.popen("ssh -i /etc/hubzero/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)
    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):

  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)

  # 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)
  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/hubzero/config)" % (login, mw_user)
  os._exit(100)


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 }

  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']))

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)
  hostname=sys.argv[2]
  confirm=sys.argv[3]
  sys.exit(check_host(hostname,confirm))

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))

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