#!/bin/sh
# Run tcl from users PATH \
exec tclsh "$0" "$@"

# $Id: sguild,v 1.101 2004/06/29 03:15:57 bamm Exp $ #

# Copyright (C) 2002-2003 Robert (Bamm) Visscher <bamm@satx.rr.com>
#
# This program is distributed under the terms of version 1.0 of the 
# Q Public License.  See LICENSE.QPL for further details.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

########################## GLOBALS ##################################

set VERSION "SGUIL-0.5.0"
# DB Version
set DB_VERSION "0.9"
# Counter for tracking xscript transactions
set TRANS_ID 0

# Config options moved to sguild.conf

######################## END GLOBALS ################################

########################## PROCS ####################################

proc ForkLoader {} {
  global DEBUG loaderWritePipe
  # First create some pipes to communicate thru
  pipe loaderReadPipe loaderWritePipe
  # Fork the child
  if {[set childPid [fork]] == 0 } {
    # We are the child now.
    proc ParentCmdRcvd { pipeID } {
      global DEBUG
      fconfigure $pipeID -buffering line
      if { [eof $pipeID] || [catch {gets $pipeID data}] } {
        exit
      } else {
        set fileName [lindex $data 0]
        set cmd [lindex $data 1]
        if [catch {eval exec $cmd} loadError] {
          puts "Unable to load PS data into DB."
          puts $loadError
        } else {
          file delete $fileName
          if {$DEBUG} {puts "$cmd"}
        }
      }
    }
    fileevent $loaderReadPipe readable [list ParentCmdRcvd $loaderReadPipe]
    if {$DEBUG} { puts "Loader Forked" }
  }
  return $childPid
}
proc DisplayUsage { cmdName } {
  puts "Usage: $cmdName \[-D\] \[-h\] \[-o\] \[-c <filename>\] \[-u <filename>\] \[-P <filename>\]"
  puts "         \[-O <filename>\] \[-C <directory\]"
  puts "       $cmdName \[-u <filename\] \[-adduser <username>\] \[-deluser <username\]"
  puts "  -c <filename>: PATH to the sguild config (sguild.conf) file."
  puts "  -a <filename>: PATH to the sguild config (autocat.conf) file."
  puts "  -g <filename>: PATH to the sguild global queries (sguild.queries) file."
  puts "  -u <filename>: PATH to the sguild users (sguild.users) file."
  puts "  -P <filename>: Name of file to write the PID to."
  puts "                 Default is /var/run/sguild.pid"
  puts "  -o Enable OpenSSL"
  puts "  -O <filename>: Enable OpenSSL using PATH to tls (tcl openssl) lib (libtls1.4.so)"
  puts "  -C <directory>: Directory that contains sguild.pem and sguild.key"
  puts "  -D Runs sguild in daemon mode."
  puts "  -adduser <username>: Add user to sguild.users"
  puts "  -deluser <username>: Delete user from sguild.users"
  puts "  -A <filename>: PATH to sguild.access file."
  puts "  -h Display this help"
  CleanExit
}
proc HupTrapped {} {
  global DEBUG AUTOCAT_FILE GLOBAL_QRY_FILE GLOBAL_QRY_LIST clientList
  global acRules acCat ACCESS_FILE
  if { $DEBUG } { puts "HUP signal caught." }
  # Reload auto cat rules
  if { $DEBUG } { puts "Reloading AutoCat rules: $AUTOCAT_FILE" }
  # Clear the current rules
  if [info exists acRules] { unset acRules }
  if [info exists acCat] { unset acCat }
  if { [ file exists $AUTOCAT_FILE] } {
    LoadAutoCatFile $AUTOCAT_FILE
  }
  # reload global queries.
  if { $DEBUG } { puts "Reloaded Global Queries: $GLOBAL_QRY_FILE" }
  # Clear the current list
  set GLOBAL_QRY_LIST ""
  if { [file exists $GLOBAL_QRY_FILE] } {
    LoadGlobalQueries $GLOBAL_QRY_FILE
  } else {
    set GLOBAL_QRY_LIST none
  }
  foreach clientSocket $clientList {
    SendSocket $clientSocket "GlobalQryList $GLOBAL_QRY_LIST"
  }
  LoadAccessFile $ACCESS_FILE
}
proc LoadAutoCatFile { filename } {
  set i 0
  for_file line $filename {
    if ![regexp ^# $line] {
      set cTime [ctoken line "||"]
      if { $cTime != "none" && $cTime != "NONE" } {
        set cTimeSecs [clock scan "$cTime" -gmt true]
        if { $cTimeSecs > [clock seconds] } {
          # Set up the removal
          set DELAY [expr ($cTimeSecs - [clock seconds]) * 1000]
          after $DELAY RemoveAutoCatRule $i
          AddAutoCatRule $line $i
          incr i
        }
      } else {
        AddAutoCatRule $line $i
        incr i
      }
    }
  }
}
# Load up the access lists.
proc LoadAccessFile { filename } {
  global CLIENT_ACCESS_LIST SENSOR_ACCESS_LIST DEBUG
  if {$DEBUG} { puts "Loading access list: $filename " }
  set CANYFLAG 0
  set SANYFLAG 0
  for_file line $filename {
    # Ignore comments (#) and blank lines.
    if { ![regexp ^# $line] && ![regexp {^\s*$} $line] } {
      if { [regexp {^\s*client} $line] && $CANYFLAG != "1" } {
        set ipaddr [lindex $line 1]
        if { $ipaddr == "ANY" || $ipaddr == "any" } {
          set CANYFLAG 1
          set CLIENT_ACCESS_LIST ANY
          if {$DEBUG} { puts "Client access list set to ALLOW ANY." }
        } else {
          if {$DEBUG} { puts "Adding client to access list: $ipaddr" }
          lappend CLIENT_ACCESS_LIST $ipaddr
        }
      } elseif { [regexp {^\s*sensor} $line] && $SANYFLAG != "1" } {
        set ipaddr [lindex $line 1]
        if { $ipaddr == "ANY" || $ipaddr == "any" } {
          set SANYFLAG 1
          set SENSOR_ACCESS_LIST ANY
          if {$DEBUG} { puts "Sensor access list set to ALLOW ANY." }
        } else {
          if {$DEBUG} { puts "Adding sensor to access list: $ipaddr" }
          lappend SENSOR_ACCESS_LIST $ipaddr
        }
      } else {
        if {$DEBUG} { puts "ERROR: Parsing $filename: Format error: $line" }
      }
    }
  }
  if {![info exists CLIENT_ACCESS_LIST] || $CLIENT_ACCESS_LIST == "" } {
    puts "ERROR: No client access lists found in $filename."
    CleanExit
  }
  if {![info exists SENSOR_ACCESS_LIST] || $SENSOR_ACCESS_LIST == "" } {
    puts "ERROR: No sensor access lists found in $filename."
    CleanExit
  }
  
  if {$DEBUG} { puts ": Done " }
}
proc RemoveAutoCatRule { rid } {
  global acRules acCat DEBUG
  if {$DEBUG} { puts "Removing Rule: $acRules($rid)" }
  unset acRules($rid)
  unset acCat($rid)
}
proc AddAutoCatRule { line rid } {
  global acRules acCat DEBUG
  if {$DEBUG} {puts "Adding AutoCat Rule: $line"}
  foreach dIndex [list 3 8 11 9 12 10 7] {
    set tmpVar [ctoken line "||"]
    if { $tmpVar != "any" && $tmpVar != "ANY" } {
      lappend acRules($rid) [list $dIndex $tmpVar]
    }
  }
  set acCat($rid) [ctoken line "||"]
}
proc AutoCat { data } {
  global acRules acCat DEBUG AUTOID
  foreach rid [array names acRules] {
    set MATCH 1
    foreach rule [lrange $acRules($rid) 0 end] {
      set rIndex [lindex $rule 0]
      set rMatch [lindex $rule 1]
      if { [lindex $data $rIndex] != $rMatch } {
        set MATCH 0
        break
      } 
    }
    if { $MATCH } {
      if {$DEBUG} {puts "AUTO MARKING EVENT AS : $acCat($rid)"}
      UpdateDBStatus "[lindex $data 5].[lindex $data 6]" [GetCurrentTimeStamp] $AUTOID $acCat($rid)
      InsertHistory [lindex $data 5] [lindex $data 6] $AUTOID [GetCurrentTimeStamp] $acCat($rid) "Auto Update"
      return 1
    }
  }
  return 0
}
proc CleanExit {} {
  global PID_FILE FORKD_PIDS
  if { [info exists PID_FILE] && [file exists $PID_FILE] } {
    if [catch {file delete -force $PID_FILE} delError] {
      puts "ERROR: $delError"
    }
  }
  if { [info exists FORKD_PIDS] } {
    puts "killing child procs..."
    foreach PID $FORKD_PIDS {
      kill $PID
    }
  }
  puts "Exiting..."
  exit
}
proc CreateUsersFile { fileName } {
  set dirName [file dirname $fileName]
  if { ![file exists $dirName] || ![file isdirectory $dirName] } {
    if [catch {file mkdir $dirName} dirError] {
      puts "Error: Could not create $dirName: $dirError"
      CleanExit
    }
  }
  if [catch {open $fileName w} fileID] {
    puts "Error: Could not create $fileName: $fileID"
    CleanExit
  } else {
    puts $fileID "#"
    puts $fileID "# WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING"
    puts $fileID "#"
    puts $fileID "# This file is automatically generated. Please do not edit it by hand."
    puts $fileID "# Doing so could corrupt the file and make it unreadable. Only when used"
    puts $fileID "# with OpenSSL enabled, is this considered secure."
    puts $fileID "#"
    puts $fileID "# WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING"
    puts $fileID "#"
    close $fileID
  }
}
proc DelUser { userName USERS_FILE } {
  set fileID [open $USERS_FILE r]
  set USERFOUND 0
  for_file line $USERS_FILE {
    if { ![regexp ^# $line] && ![regexp ^$ $line] } {
      # User file is boobie deliminated
      set tmpLine $line
      if { $userName == [ctoken tmpLine "(.)(.)"] } {
        set USERFOUND 1
      } else {
        lappend tmpData $line
      }
    } else {
      lappend tmpData $line
    }
  }
  close $fileID
  if { !$USERFOUND } {
    puts "ERROR: User \'$userName\' does NOT exist in $USERS_FILE"
  } else {
    if [catch {open $USERS_FILE w} fileID] {
      puts "ERROR: Could not edit $USERS_FILE: $fileID"
    } else {
      foreach line $tmpData {
        puts $fileID $line
      }
      close $fileID
    }
  }
}
proc AddUser { userName USERS_FILE } {
  # Usernames must be alpha-numeric
  if { ![string is alnum $userName] } {
    puts "ERROR: Unsername must be alpha-numeric"
    return
  }
  # Make sure we aren't adding a dupe.
  set fileID [open $USERS_FILE r]
  for_file line $USERS_FILE {
    if { ![regexp ^# $line] && ![regexp ^$ $line] } {
      # User file is boobie deliminated
      if { $userName == [ctoken line "(.)(.)"] } {
        puts "ERROR: User \'$userName\' already exists in $USERS_FILE."
        return
      }
    }
  }
  close $fileID
  # Get a passwd
  puts -nonewline "Please enter a passwd for $userName: "
  flush stdout
  exec stty -echo
  set passwd1 [gets stdin]
  exec stty echo
  puts -nonewline "\nRetype passwd: "
  flush stdout
  exec stty -echo
  set passwd2 [gets stdin]
  exec stty echo
  puts ""
  if { $passwd1 != $passwd2} {
    puts "ERROR: Passwords didn't match."
    puts "$USERS_FILE NOT updated."
    return
  }
  set salt [format "%c%c" [GetRandAlphaNumInt] [GetRandAlphaNumInt] ]
  # make a hashed passwd
  set hashPasswd [::sha1::sha1 "${passwd1}${salt}"]
  set fileID [open $USERS_FILE a]
  puts $fileID "${userName}(.)(.)${salt}${hashPasswd}"
  close $fileID
  puts "User \'$userName\' added successfully"
}
proc GetRandAlphaNumInt {} {
  set x [expr [random 74] + 48]
  while {!($x >= 48 && $x <= 57) && !($x >= 65 && $x <= 90)\
      && !($x >= 97 && $x <= 122)} {
     set x [expr [random 74] + 48]
  }
  return $x
}
# Reads file and adds queries to GLOBAL_QRY_LIST
proc LoadGlobalQueries { fileName } {
  global GLOBAL_QRY_LIST
  for_file line $fileName {
    if { ![regexp ^# $line] && ![regexp ^$ $line] } {
      lappend GLOBAL_QRY_LIST $line
    }
  }
}
#
# CheckLineFormat - Parses CONF_FILE lines to make sure they are formatted
#                   correctly (set varName value). Returns 1 if good.
#
proc CheckLineFormat { line } {
  
  set RETURN 1
  # Right now we just check the length and for "set".
  if { [llength $line] != 3 || [lindex $line 0] != "set" } { set RETURN 0 }
  return $RETURN
}   

proc ValidateUser { socketID username } {
  global USERS_FILE validSockets socketInfo userIDArray
  fileevent $socketID readable {}
  fconfigure $socketID -buffering line
  if { ![file exists $USERS_FILE] } {
    puts "Fatal Error! Cannot access $USERS_FILE."
    CleanExit
  } 
  set VALID 0
  set nonce [format "%c%c%c" [GetRandAlphaNumInt] [GetRandAlphaNumInt] [GetRandAlphaNumInt] ]
  for_file line $USERS_FILE {
    if { ![regexp ^# $line] && ![regexp ^$ $line] } {
      # Probably should check for corrupted info here
      set tmpUserName [ctoken line "(.)(.)"]
      set tmpSaltHash [ctoken line "(.)(.)"]
      if { $tmpUserName == $username } {
        set VALID 1
        set tmpSalt [string range $tmpSaltHash 0 1]
        set finalCheck [::sha1::sha1 "${nonce}${tmpSaltHash}"]
        break
      }
    }
  }
  if {$VALID} {
    puts $socketID "$tmpSalt $nonce"
    set finalClient [gets $socketID]
    if { $finalClient == $finalCheck } {
      set userIDArray($socketID) [GetUserID $username]
      DBCommand\
       "UPDATE user_info SET last_login='[GetCurrentTimeStamp]' WHERE uid=$userIDArray($socketID)"
      lappend validSockets $socketID
      catch { SendSocket $socketID "UserID $userIDArray($socketID)" } tmpError
      SendSystemInfoMsg sguild "User $username logged in from [lindex $socketInfo($socketID) 0]"
      lappend socketInfo($socketID) $username
    } else {
      set validSockets [ldelete $validSockets $socketID]
      catch {SendSocket $socketID "UserID INVALID"} tmpError
      SendSystemInfoMsg sguild "User $username denied access from [lindex $socketInfo($socketID) 0]"
    } 
  } else {
    #Not a valid user. Make up info.
    set tmpSalt [format "%c%c" [GetRandAlphaNumInt] [GetRandAlphaNumInt] ]
    set finalCheck [::sha1::sha1 "${nonce}${tmpSalt}"]
    puts $socketID "$tmpSalt $nonce"
    set finalClient [gets $socketID]
    set validSockets [ldelete $validSockets $socketID]
    catch {SendSocket $socketID "UserID INVALID"} tmpError
    SendSystemInfoMsg sguild "User $username denied access from [lindex $socketInfo($socketID) 0]"
  }
  fileevent $socketID readable [list ClientCmdRcvd $socketID]
}
proc GetUserID { username } {
  set uid [FlatDBQuery "SELECT uid FROM user_info WHERE username='$username'"]
  if { $uid == "" } {
    DBCommand\
     "INSERT INTO user_info (username, last_login) VALUES ('$username', '[GetCurrentTimeStamp]')"
    set uid [FlatDBQuery "SELECT uid FROM user_info WHERE username='$username'"]
  }
  return $uid
}
#
# ClientCmdRcvd: Called when client sends commands.
#
proc ClientCmdRcvd { socketID } {
  global DEBUG clientList validSockets GLOBAL_QRY_LIST

  if { [eof $socketID] || [catch {gets $socketID data}] } {
    # Socket closed
    close $socketID
    ClientExitClose $socketID
    if {$DEBUG} { puts "Socket $socketID closed" }
  } else {
    if {$DEBUG} {puts "Clent Command Recieved: $data"}
    set origData $data
    set clientCmd [ctoken data " "]
    # Check to make the client validated itself
    if { $clientCmd != "ValidateUser" && $clientCmd != "PING" } {
      if { [lsearch -exact $validSockets $socketID] < 0 } {
        catch {SendSocket $socketID\
         "InfoMessage {Client does not appear to be logged in. Please exit and log back in.}"} tmpError
      return
      }
    }
    set data1 [string trimleft $data]
    # data1 will contain the list from index 1 on. 
    set index1 [ctoken data " "]
    set data2 [string trimleft $data]
    # data2 now contains only indices 2 on, because ctoken chops tokens off
    set index2 [ctoken data " "]
    # data3 now contains indicies 3 on
    set data3 [string trimleft $data]
    set index3 [ctoken data " "]
    set index4 [ctoken data " "]
    switch -exact $clientCmd {
      DeleteEventID { $clientCmd $socketID $index1 $index2 }
      DeleteEventIDList { $clientCmd $socketID $data1 }
      EventHistoryRequest { $clientCmd $socketID $index1 $index2 $data3 }
      ExecDB { $clientCmd $socketID $data1 }
      GetCorrelatedEvents { $clientCmd $socketID $index1 $index2 }
      GetIcmpData { $clientCmd $socketID $index1 $index2 }
      GetIPData { $clientCmd $socketID $index1 $index2 }
      GetPayloadData { $clientCmd $socketID $index1 $index2 }
      GetPSData { $clientCmd $socketID $index1 $index2 $data3 }
      GetTcpData { $clientCmd $socketID $index1 $index2 }
      GetUdpData { $clientCmd $socketID $index1 $index2 }
      MonitorSensors { $clientCmd $socketID $data1 } 
      QueryDB { $clientCmd $socketID $index1 $data2 }
      RuleRequest { $clientCmd $socketID $index1 $data2 }
      SendSensorList { $clientCmd $socketID }
      SendEscalatedEvents { $clientCmd $socketID }
      SendDBInfo { $clientCmd $socketID }
      ValidateUser { ValidateUser $socketID $index1 }
      PING { puts $socketID "PONG" }
      UserMessage { UserMsgRcvd $socketID $data1 }
      SendGlobalQryList { SendSocket $socketID "GlobalQryList $GLOBAL_QRY_LIST" }
      ReportRequest { ReportBuilder $socketID $index1 $index2 $index3 }
      GetSancpFlagData { $clientCmd $socketID $index1 $index2 }
      XscriptRequest { eval $clientCmd $socketID $data1 }
      EtherealRequest { eval $clientCmd $socketID $data1 }
      default { if {$DEBUG} {puts "Unrecognized command from $socketID: $origData"} }
    }
  }
}
proc InitRawFileArchive { date sensor srcIP dstIP srcPort dstPort ipProto } {
  global LOCAL_LOG_DIR DEBUG
  # Check to make sure our dirs exists. We use <rootdir>/date/sensorName/*.raw
  set dateDir "$LOCAL_LOG_DIR/$date"
  if { ! [file exists $dateDir] } {
    file mkdir $dateDir
  }
  set sensorDir "$dateDir/$sensor"
  if { ![file exists $sensorDir] } {
    file mkdir $sensorDir
  }
  # We always make the highest port the apparent source. This way we don't store
  # two copies of the same raw data.
  if { $srcPort > $dstPort } {
    set rawDataFileName "${srcIP}:${srcPort}_${dstIP}:${dstPort}-${ipProto}.raw"
  } else {
    set rawDataFileName "${dstIP}:${dstPort}_${srcIP}:${srcPort}-${ipProto}.raw"
  }
  return [list $sensorDir $rawDataFileName]
}
proc EtherealRequest { socketID sensor timestamp srcIP srcPort dstIP dstPort ipProto force } {
  global TRANS_ID transInfoArray DEBUG LOCAL_LOG_DIR
    # Increment the xscript counter. Gives us a unique way to track the xscript
  incr TRANS_ID
  set date [lindex $timestamp 0]
  set rawDataFileNameInfo [InitRawFileArchive $date $sensor $srcIP $dstIP $srcPort $dstPort $ipProto]
  set sensorDir [lindex $rawDataFileNameInfo 0]
  set rawDataFileName [lindex $rawDataFileNameInfo 1]
  # A list of info we'll need when we generate the actual xscript after the rawdata is returned.
  set transInfoArray($TRANS_ID) [list $socketID null $sensorDir ethereal $sensor $timestamp ]
  if { ! [file exists $sensorDir/$rawDataFileName] || $force } {
    # No local archive (first request) or the user has requested we force a check for new data.
    if { ![GetRawDataFromSensor $TRANS_ID $sensor $timestamp $srcIP $srcPort $dstIP $dstPort $ipProto $rawDataFileName ethereal] } {
      # This means the sensor_agent for this sensor isn't connected.
      SendSocket $socketID "ErrorMessage ERROR: Unable to request rawdata at this time.\
       The sensor $sensor is NOT connected."
    }
  } else {
    # The data is archived locally.
    SendEtherealData $sensorDir/$rawDataFileName $TRANS_ID
  }

}
proc SendEtherealData { fileName TRANS_ID } {
  global DEBUG transInfoArray
  
  set clientSocketID [lindex $transInfoArray($TRANS_ID) 0]
  #puts $clientSocketID "EtherealDataBase64 [file tail $fileName] [file size $fileName]"
  puts $clientSocketID "EtherealDataPcap [file tail $fileName] [file size $fileName]"
  set rFileID [open $fileName r]
  fconfigure $rFileID -translation binary
  fconfigure $clientSocketID -translation binary
  fcopy $rFileID $clientSocketID
  fconfigure $clientSocketID -encoding utf-8 -translation {auto crlf}
  #sock12 null /snort_data/archive/2004-06-10/gateway ethereal gateway {2004-06-10 17:21:56}
  #SendSocket $clientSocketID "EtherealDataBase64 [file tail $fileName] BEGIN"
  #set inFileID [open $fileName r]
  #fconfigure $inFileID -translation binary
  #foreach line [::base64::encode [read -nonewline $inFileID]] {
  #  SendSocket $clientSocketID "EtherealDataBase64 [file tail $fileName] $line"
  #}
  #SendSocket $clientSocketID "EtherealDataBase64 [file tail $fileName] END"
  #close $inFileID
}
proc XscriptRequest { socketID sensor winID timestamp srcIP srcPort dstIP dstPort force } {
  global TRANS_ID transInfoArray DEBUG LOCAL_LOG_DIR TCPFLOW
  # If we don't have TCPFLOW then error to the user and return
  if { ![info exists TCPFLOW] || ![file exists $TCPFLOW] || ![file executable $TCPFLOW] } {
      SendSocket $socketID "ErrorMessage ERROR: tcpflow is not installed on the server."
      SendSocket $socketID "XscriptDebugMsg $winID ERROR: tcpflow is not installed on the server."
    return
  }
  # Increment the xscript counter. Gives us a unique way to track the xscript
  incr TRANS_ID
  set date [lindex $timestamp 0]
  set rawDataFileNameInfo [InitRawFileArchive $date $sensor $srcIP $dstIP $srcPort $dstPort 6]
  set sensorDir [lindex $rawDataFileNameInfo 0]
  set rawDataFileName [lindex $rawDataFileNameInfo 1]
  # A list of info we'll need when we generate the actual xscript after the rawdata is returned.
  set transInfoArray($TRANS_ID) [list $socketID $winID $sensorDir xscript $sensor $timestamp ]
  if { ! [file exists $sensorDir/$rawDataFileName] || $force } {
    # No local archive (first request) or the user has requested we force a check for new data.
    if { ![GetRawDataFromSensor $TRANS_ID $sensor $timestamp $srcIP $srcPort $dstIP $dstPort 6 $rawDataFileName xscript] } {
      # This means the sensor_agent for this sensor isn't connected.
      SendSocket $socketID "ErrorMessage ERROR: Unable to request xscript at this time.\
       The sensor $sensor is NOT connected."
      SendSocket $socketID "XscriptDebugMsg $winID ERROR: Unable to request xscript at this time.\
       The sensor $sensor is NOT connected."
    }
  } else {
    # The data is archive locally.
    SendSocket $socketID "XscriptDebugMsg $winID Using archived data: $sensorDir/$rawDataFileName"
    GenerateXscript $sensorDir/$rawDataFileName $socketID $winID 
  }
}
proc GetRawDataFromSensor { TRANS_ID sensor timestamp srcIP srcPort dstIP dstPort proto filename type } {
  global agentSocket connectedAgents DEBUG transInfoArray
  set RFLAG 1
  if { [array exists agentSocket] && [info exists agentSocket($sensor)]} {
    set sensorSocketID $agentSocket($sensor)
    if {$DEBUG} {
      puts "Sending $sensor: RawDataRequest $TRANS_ID $sensor $timestamp $srcIP $dstIP $dstPort $proto $filename $type"
    }
    if { [catch { puts $sensorSocketID\
         "[list RawDataRequest $TRANS_ID $sensor $timestamp $srcIP $dstIP $srcPort $dstPort $proto $filename $type]" }\
          sendError] } {
      catch { close $sensorSocketID } tmpError
      CleanUpDisconnectedAgent $sensorSocketID
      set RFLAG 0 
    }
    flush $sensorSocketID
    if { $type == "xscript" } {
      SendSocket [lindex $transInfoArray($TRANS_ID) 0]\
       "XscriptDebugMsg [lindex $transInfoArray($TRANS_ID) 1] Raw data request sent to $sensor."
    }
  } else {
    set RFLAG 0
  }
  return $RFLAG
}
proc RawDataFile { socketID fileName TRANS_ID } {
  global DEBUG agentSensorName transInfoArray
  set type [lindex $transInfoArray($TRANS_ID) 3]
  if {$DEBUG} {puts "Recieving rawdata file $fileName."}
  if { $type == "xscript" } {
    SendSocket [lindex $transInfoArray($TRANS_ID) 0]\
     "XscriptDebugMsg [lindex $transInfoArray($TRANS_ID) 1] Recieving raw file from sensor."
  }
  fconfigure $socketID -translation binary
  set outfile [lindex $transInfoArray($TRANS_ID) 2]/$fileName
  set fileID [open $outfile w]
  fconfigure $fileID -translation binary
  # Copy the file from the binary socket
  fcopy $socketID $fileID
  catch {close $fileID}
  catch {close $socketID}
  if { $type == "xscript" } {
    GenerateXscript $outfile [lindex $transInfoArray($TRANS_ID) 0] [lindex $transInfoArray($TRANS_ID) 1]
  } elseif { $type == "ethereal" } {
    SendEtherealData $outfile $TRANS_ID
  }
}
proc XscriptDebugMsg { TRANS_ID msg } {
  global DEBUG transInfoArray
  SendSocket [lindex $transInfoArray($TRANS_ID) 0]\
     "XscriptDebugMsg [lindex $transInfoArray($TRANS_ID) 1] $msg"
}
proc GenerateXscript { fileName clientSocketID winName } {
  global TRANS_ID transInfoArray TCPFLOW DEBUG LOCAL_LOG_DIR P0F P0F_PATH 
  set NODATAFLAG 1
  # We don't have a really good way for make xscripts yet and are unable
  # to figure out the true src. So we assume the low port was the server
  # port. We can get that info from the file name.
  # Filename example: 208.185.243.68:6667_67.11.255.148:3470-6.raw
  regexp {^(.*):(.*)_(.*):(.*)-([0-9]+)\.raw$} [file tail $fileName] allMatch srcIP srcPort dstIP dstPort ipProto
  
  set srcMask [TcpFlowFormat $srcIP $srcPort $dstIP $dstPort]
  set dstMask [TcpFlowFormat $dstIP $dstPort $srcIP $srcPort]
  SendSocket $clientSocketID "XscriptMainMsg $winName HDR"
  SendSocket $clientSocketID "XscriptMainMsg $winName Sensor Name:\t[lindex $transInfoArray($TRANS_ID) 4]"
  SendSocket $clientSocketID "XscriptMainMsg $winName Timestamp:\t[lindex $transInfoArray($TRANS_ID) 5]"
  SendSocket $clientSocketID "XscriptMainMsg $winName Connection ID:\t$winName"
  SendSocket $clientSocketID "XscriptMainMsg $winName Src IP:\t\t$srcIP\t([GetHostbyAddr $srcIP])"
  SendSocket $clientSocketID "XscriptMainMsg $winName Dst IP:\t\t$dstIP\t([GetHostbyAddr $dstIP])"
  SendSocket $clientSocketID "XscriptMainMsg $winName Src Port:\t\t$srcPort"
  SendSocket $clientSocketID "XscriptMainMsg $winName Dst Port:\t\t$dstPort"
  if {$P0F} {
    if { ![file exists $P0F_PATH] || ![file executable $P0F_PATH] } {
      SendSocket $clientSocketID "XscriptDebugMsg $winName Cannot find p0f in: $P0F_PATH"
      SendSocket $clientSocketID "XscriptDebugMsg $winName OS fingerprint has been disabled"
    } else {
      set p0fID [open "| $P0F_PATH -q -s $fileName"]
      while { [gets $p0fID data] >= 0 } {
        SendSocket $clientSocketID "XscriptMainMsg $winName OS Fingerprint:\t$data"
      }
      catch {close $p0fID} closeError
    }
  }
  # Depreciated with hdrTag in sguil.tk
  #SendSocket $clientSocketID "XscriptMainMsg $winName ================================================================================="
  SendSocket $clientSocketID "XscriptMainMsg $winName \n"
  if  [catch {open "| $TCPFLOW -c -r $fileName"} tcpflowID] {
    if {$DEBUG} {puts "ERROR: tcpflow: $tcpflowID"}
    SendSocket $clientSocketID "XscriptDebugMsg $winName ERROR: tcpflow: $tcpflowID"
    catch {close $tcpflowID} 
    return
  }
  set state SRC
  while { [gets $tcpflowID data] >= 0 } {
    set NODATAFLAG 0
    if { [regsub ^$srcMask:\  $data {} data] > 0 } {
      set state SRC
    } elseif { [regsub ^$dstMask:\  $data {} data] > 0 } {
      set state DST
    }
    SendSocket $clientSocketID "XscriptMainMsg $winName $state"
    SendSocket $clientSocketID "XscriptMainMsg $winName $data"
  }
  if [catch {close $tcpflowID} closeError] {
    SendSocket $clientSocketID "XscriptDebugMsg $winName ERROR: tcpflow: $closeError"
  }
  if {$NODATAFLAG} {
    SendSocket $clientSocketID "XscriptMainMsg $winName No Data Sent."
  }
  SendSocket $clientSocketID "XscriptMainMsg $winName DONE"
  unset transInfoArray($TRANS_ID)
}

#
# GetHostbyAddr: uses extended tcl (wishx) to get an ips hostname
#                May move to a server func in the future
#
proc GetHostbyAddr { ip } {
  if [catch {host_info official_name $ip} hostname] {
    set hostname "Unknown"
  }
  return $hostname
}
proc TcpFlowFormat { srcIP srcPort dstIP dstPort } {
  set tmpSrcIP [split $srcIP .]
  set tmpDstIP [split $dstIP .]
  set tmpData [eval format "%03i.%03i.%03i.%03i.%05i-%03i.%03i.%03i.%03i.%05i" $tmpSrcIP $srcPort $tmpDstIP $dstPort]
  return $tmpData
}



proc UserMsgRcvd { socketID userMsg } {
  global socketInfo clientList

  set userMsg [lindex $userMsg 0]

  # Simple command crap.
  if { $userMsg == "who" } {
     foreach client $clientList { lappend usersList [lindex $socketInfo($client) 2] }
     SendSocket $socketID "UserMessage sguild \{Connected users: $usersList\}" 
  } else {
    foreach client $clientList {
      SendSocket $client [list UserMessage [lindex $socketInfo($socketID) 2] $userMsg]
    }
  }
}
proc ClientExitClose { socketID } {
  global clientList  clientMonitorSockets validSockets socketInfo sensorUsers
  set userName [lindex $socketInfo($socketID) 2]
  if { [info exists clientList] } {
    set clientList [ldelete $clientList $socketID]
  }
  if { [info exists clientMonitorSockets] } {
    foreach sensorName [array names clientMonitorSockets] {
      set clientMonitorSockets($sensorName) [ldelete $clientMonitorSockets($sensorName) $socketID]
    }
  }
  if { [info exists validSockets] } {
    set validSockets [ldelete $validSockets $socketID]
  }
  if { [array exists sensorUsers] } {
    foreach sensorName [array names sensorUsers] {
      set sensorUsers($sensorName) [ldelete $sensorUsers($sensorName) $userName]
    }
  }
  if { [info exists socketInfo($socketID)] } {
    SendSystemInfoMsg sguild "User [lindex $socketInfo($socketID) 2] has disconnected."
    unset socketInfo($socketID)
  }
}
proc FormatStdToQuery { status priority class sensor time sid cid msg sip dip proto sp dp } {
  return "[list $status $priority $sensor $time $sid $cid $msg $sip $dip $proto $sp $dp]"
}
proc GetCorrelatedEvents { socketID eid winName } {
  global correlatedEventArray eventIDArray
  if { [info exists eventIDArray] } {
    SendSocket $socketID "InsertQueryResults $winName [eval FormatStdToQuery $eventIDArray($eid)]"
  }
  if { [info exists correlatedEventArray($eid)] } {
    foreach row $correlatedEventArray($eid) {
      SendSocket $socketID "InsertQueryResults $winName [eval FormatStdToQuery $row]"
    } 
  }
}
proc CorrelateEvent { srcip msg } {
  global eventIDArray eventIDList eventIDCountArray
  set MATCH 0
  # Loop thru the RTEVENTS for a match on srcip msg
  foreach rteid $eventIDList {
    if { [lindex $eventIDArray($rteid) 8] == $srcip && [lindex $eventIDArray($rteid) 7] == $msg } {
      # Have a match
      set MATCH $rteid
    }
  }
  return $MATCH
}
#
# EventRcvd: Called by main when events are received.
#
proc EventRcvd { socketID data } {
  global DEBUG EMAIL_EVENTS EMAIL_CLASSES EMAIL_DISABLE_SIDS EMAIL_ENABLE_SIDS eventIDCountArray
  global acRules acCat correlatedEventArray
  set eventDataList [lrange [split $data |] 1 13]
  if { [lindex $eventDataList 2] == "system-info" } {
    if {$DEBUG} {
      puts "SYSTEM INFO: $eventDataList"
    }
    puts $socketID "CONFIRM system msg"
    set sensorName [lindex $eventDataList 3]
    set message [lindex $eventDataList 5]
    SendSystemInfoMsg $sensorName $message
  } else {
    if {$DEBUG} {
      puts "Alert Received: $eventDataList"
    }
    puts $socketID "CONFIRM [lindex $eventDataList 6]"
    flush $socketID
    # If we don't have any auto-cat rules, or we don't match on
    # the autocat, then we send off the rule
    if { ![array exists acRules] || ![AutoCat $eventDataList] } {
      # Correlation/aggregation checks here: CorrelateEvent sid aid SrcIP Message
      set matchAID [ CorrelateEvent [lindex $eventDataList 8] [lindex $eventDataList 7] ]
      if { $matchAID == 0 } {
        AddEventToEventArray $eventDataList
        SendEvent "$eventDataList 1"
        if { $EMAIL_EVENTS } {
          #Ug-ly. Things will get better when the rules are in the DB.
          set sid [lindex $eventDataList 13]
          set class [lindex $eventDataList 2]
          if { ([lsearch -exact $EMAIL_CLASSES $class] >= 0\
               && [lsearch -exact $EMAIL_DISABLE_SIDS $sid] < 0)
               || [lsearch -exact $EMAIL_ENABLE_SIDS $sid] >= 0 } {
            EmailEvent $eventDataList
          }
        }
      } else {
        # Add event to parents list
        lappend correlatedEventArray($matchAID) $eventDataList
        # Bump the parents count
        incr eventIDCountArray($matchAID)
        # Send an update notice to clients
        set sensorName [lindex $eventDataList 3]
        SendIncrEvent $matchAID $sensorName $eventIDCountArray($matchAID) [lindex $eventDataList 1]
      }
    }
  }
}
proc EmailEvent { dataList } {
  global SMTP_SERVER EMAIL_RCPT_TO EMAIL_FROM EMAIL_SUBJECT EMAIL_MSG
  global DEBUG
  set msg [lindex $dataList 7]
  set sn [lindex $dataList 3]
  set t [lindex $dataList 4]
  set sip [lindex $dataList 8]
  set dip [lindex $dataList 9]
  set sp [lindex $dataList 11]
  set dp [lindex $dataList 12]
  regsub -all {%} $EMAIL_MSG {$} tmpMsg
  set tmpMsg [subst -nobackslashes -nocommands $tmpMsg]
  if {$DEBUG} {puts "Sending Email: $tmpMsg"}
  set token [mime::initialize -canonical text/plain -string $tmpMsg]
  if { [info exists EMAIL_SUBJECT] } { mime::setheader $token Subject $EMAIL_SUBJECT }
  smtp::sendmessage $token -recipients $EMAIL_RCPT_TO -servers $SMTP_SERVER -originator $EMAIL_FROM
  mime::finalize $token
  if {$DEBUG} {puts "Email sent to: $EMAIL_RCPT_TO"}
}

proc GetCurrentTimeStamp {} {
  set timestamp [clock format [clock seconds] -gmt true -f "%Y-%m-%d %T"]
  return $timestamp 
} 


#
# ldelete: Delete item from a list
#
proc ldelete { list value } {
  set ix [lsearch -exact $list $value]
  if {$ix >= 0} {
    return [lreplace $list $ix $ix]
  } else {
    return $list
  }
}


#
# SendSocket: Send command to client
#
proc SendSocket { socketID command } {
  global clientList DEBUG
  if {$DEBUG} {puts "Sending $socketID: $command"}
  if { [catch {puts $socketID $command} sendError] } {
    if {$DEBUG} { puts "Error sending \"$command\" to $socketID" }
    catch { close $socketID } closeError
    # Remove socket from the client list
    ClientExitClose $socketID
    return -code error -errorinfo $sendError
  }
  catch {flush $socketID} flushError
}

#
# SendEvent: Send events to connected clients
#
proc SendEvent { eventDataList } {
  global DEBUG clientMonitorSockets
  set sensorName [lindex $eventDataList 3]
  if { [info exists clientMonitorSockets($sensorName)] } {
    foreach clientSocket $clientMonitorSockets($sensorName) {
      catch {SendSocket $clientSocket "InsertEvent $eventDataList"} tmpError
    }
  } else {
    if {$DEBUG} { puts "No clients to send alert to." }
  }
}
proc SendIncrEvent { eid sensorName count priority} {
  global DEBUG clientMonitorSockets
  if { [info exists clientMonitorSockets($sensorName)] } {
    foreach clientSocket $clientMonitorSockets($sensorName) {
      catch {SendSocket $clientSocket "IncrEvent $eid $count $priority"} tmpError
    }
  } else {
    if {$DEBUG} { puts "No clients to send msg to." }
  }
}
proc SendSystemInfoMsg { sensor msg } {
  global clientList DEBUG
  if { [info exists clientList] && [llength $clientList] > 0 } {
    foreach clientSocket $clientList {
      catch {SendSocket $clientSocket "InsertSystemInfoMsg $sensor $msg"} tmpError
    }
  } else {
    if {$DEBUG} { puts "No clients to send info msg to." }
  }
}
#
# AddEventToEventArray: Global eventIDArray contains current events.
#
proc AddEventToEventArray { eventDataList } {
  global eventIDArray eventIDList sensorIDList eventIDCountArray
  set eventID [join [lrange $eventDataList 5 6] .]
  set eventIDCountArray($eventID) 1
  set sensorName [lindex $eventDataList 3]
  set eventIDArray($eventID) $eventDataList
  # Arrays are not kept in any particular order so we have to keep
  # a list in order to control the order the clients recieve events
  lappend eventIDList $eventID
}

proc DeleteEventIDList { socketID data } {
  global eventIDArray eventIDList clientList escalateArray escalateIDList
  global userIDArray correlatedEventArray eventIDCountArray 

  regexp {^(.*)::(.*)::(.*)$} $data allMatch status comment eidList
  set updateTmp ""
  foreach socket $clientList {
    # Sending a DeleteEventID to the originating client allows us
    # to remove events from the RT panes when deleting from a query.
    # Problem is, we could delete a correlated event parent without
    # deleting the children thus leaving alerts that haven't been 
    # dealt with. 
    catch {SendSocket $socket "DeleteEventIDList $eidList"} tmpError
  }
  foreach eventID $eidList {
    set tmpSid [lindex [split $eventID .] 0]
    set tmpCid [lindex [split $eventID .] 1]
    # updateTmp contains the constraints of our UPDATE query
    set updateTmp "$updateTmp (sid=$tmpSid AND cid=$tmpCid)"
    if { [info exists escalateIDList] } {set escalateIDList [ldelete $escalateIDList $eventID]}
    lappend tmpEidList $eventID
    # loop through the parents array and add all the sids/cids to the UPDATE
    if [info exists correlatedEventArray($eventID)] {
      foreach row $correlatedEventArray($eventID) {
        set tmpSid [lindex $row 5]
        set tmpCid [lindex $row 6]
        set updateTmp "$updateTmp (sid=$tmpSid AND cid=$tmpCid)"
        if { [info exists escalateIDList] } {
          set escalateIDList [ldelete $escalateIDList "$tmpSid.$tmpCid"]
        }
        # Tmp list of eids
        lappend tmpEidList "$tmpSid.$tmpCid"
      }
    }
  }  
  # Number of events we should be updating
  set eidListSize [llength $tmpEidList]
  # Now we have a complete list of event IDs. Loop thru them and update our current VARs
  # and send escalate notices if needed
  foreach tmpEid $tmpEidList {
    # If status == 2 then escalate
    if {$status == 2} {
      lappend escalateIDList $tmpEid
      if [info exists eventIDArray($tmpEid)] {
        set escalateArray($tmpEid) $eventIDArray($tmpEid)
      } else {
        set escalateArray($tmpEid) [FlatDBQuery\
         "SELECT event.status, event.priority, event.class, sensor.hostname, event.timestamp, event.sid, event.cid, event.signature, INET_NTOA(event.src_ip), INET_NTOA(event.dst_ip), event.ip_proto, event.src_port, event.dst_port FROM event, sensor WHERE event.sid=sensor.sid AND event.sid=[lindex [split $tmpEid .] 0] AND event.cid=[lindex [split $tmpEid .] 1]"]
      }
      foreach socket $clientList {
        catch {SendSocket $socket "InsertEscalatedEvent $escalateArray($tmpEid)"} tmpError
      }
    }
    # Cleanup
    if { [info exists eventIDArray($tmpEid)] } { unset eventIDArray($tmpEid) }
    if { [info exists correlatedEventArray($tmpEid)] } { unset correlatedEventArray($tmpEid) }
    if { [info exists eventIDCountArray($tmpEid)] } { unset eventIDCountArray($tmpEid) }
    if [info exists eventIDList] {
      set eventIDList [ldelete $eventIDList $tmpEid]
    }
  }
  # Finally we update the DB
  regsub -all {\) \(} $updateTmp {) OR (} whereTmp
  set tmpUpdated [UpdateDBStatusList $whereTmp [GetCurrentTimeStamp] $userIDArray($socketID) $status]
  # See if the number of rows updated matched the number of events we 
  # meant to update
  if { $tmpUpdated != $eidListSize } {
    catch {SendSocket $socketID "ErrorMessage ERROR: Some events may not have been updated. Event(s) may be missing from DB. See sguild output for more information."} tmpError
    puts "ERROR: Number of updates mismatched number of events."
    puts "       Number of EVENTS:  $eidListSize"
    puts "       Number of UPDATES: $tmpUpdated"
    puts "       WHERE: $whereTmp"
  }
  # Update the history here
  foreach tmpEid $tmpEidList {
    set tmpSid [lindex [split $tmpEid .] 0]
    set tmpCid [lindex [split $tmpEid .] 1]
    InsertHistory $tmpSid $tmpCid $userIDArray($socketID) [GetCurrentTimeStamp] $status $comment
  } 
}

proc InsertHistory { sid cid uid timestamp status comment} {
  if {$comment == "none"} {
    DBCommand "INSERT INTO history (sid, cid, uid, timestamp, status) VALUES ( $sid, $cid, $uid, '$timestamp', $status)"
  } else {
    DBCommand "INSERT INTO history (sid, cid, uid, timestamp, status, comment) VALUES ( $sid, $cid, $uid, '$timestamp', $status, '$comment')"
  }
}

proc DeleteEventID { socketID eventID status } {
  global eventIDArray eventIDList clientList escalateArray escalateIDList
  global userIDArray
  
  foreach socket $clientList {
    # See comments in DeleteEventIDList
    catch {SendSocket $socket "DeleteEventID $eventID"} tmpError
  }
  # If status == 2 then escalate
  if { $status == 2 } {
    lappend escalateIDList $eventID
    set escalateArray($eventID) $eventIDArray($eventID)
    foreach socket $clientList {
      catch {SendSocket $socket "InsertEscalatedEvent $escalateArray($eventID)"} tmpError
    }
  }
  if { [info exists escalateArray($eventID)] } { unset escalateArray($eventID) }
  if { [info exists escalateIDList] } {set escalateIDList [ldelete $escalateIDList $eventID]}
  if { [info exists eventIDArray($eventID)] } { unset eventIDArray($eventID) }
  set eventIDList [ldelete $eventIDList $eventID]
  InsertHistory [lindex [split $eventID .] 0] [lindex [split $eventID .] 1]\
   $userIDArray($socketID) [GetCurrentTimeStamp] $status
  UpdateDBStatus $eventID [GetCurrentTimeStamp] $userIDArray($socketID) $status
}

proc SendDBInfo { socketID } {
  global tableNameList tableArray 
  catch {SendSocket $socketID "TableNameList $tableNameList"} tmpError
  foreach tableName $tableNameList {
    catch {SendSocket $socketID "TableColumns $tableName $tableArray($tableName)"} tmpError
  }
}
proc ValidateSensorAccess { ipaddr } {
  global SENSOR_ACCESS_LIST DEBUG
  if {$DEBUG} {puts -nonewline "Validating sensor access: $ipaddr : "}
  set RFLAG 0
  if { $SENSOR_ACCESS_LIST == "ANY" } {
    set RFLAG 1
  } elseif { [info exists SENSOR_ACCESS_LIST] && [lsearch -exact $SENSOR_ACCESS_LIST $ipaddr] >= 0 } {
    set RFLAG 1
  } 
  return $RFLAG
}
proc ValidateClientAccess { ipaddr } {
  global CLIENT_ACCESS_LIST DEBUG
  if {$DEBUG} {puts "Validating client access: $ipaddr"}
  set RFLAG 0
  if { $CLIENT_ACCESS_LIST == "ANY" } {
    set RFLAG 1
  } elseif { [info exists CLIENT_ACCESS_LIST] && [lsearch -exact $CLIENT_ACCESS_LIST $ipaddr] >= 0 } {
    set RFLAG 1
  } 
  return $RFLAG
}
#
# ClientConnect: Sets up comms for client/server
#
proc ClientConnect { socketID IPAddr port } {
  global DEBUG socketInfo VERSION
  global OPENSSL KEY PEM
  if {$DEBUG} {
    puts "Client Connect: $IPAddr $port $socketID"
  }
  # Check the client access list
  if { ![ValidateClientAccess $IPAddr] } {
    SendSocket $socketID "Connection Refused."
    catch {close $socketID} tmpError
    if {$DEBUG} { puts "Invalid access attempt from $IPAddr" }
    return
  }
  if {$DEBUG} { puts "Valid client access: $IPAddr" }
  set socketInfo($socketID) "$IPAddr $port"
  fconfigure $socketID -buffering line
  # Do version checks
  if [catch {SendSocket $socketID "$VERSION"} sendError ] {
    return
  }
  if [catch {gets $socketID} clientVersion] {
    if {$DEBUG} {puts "$ERROR: $clientVersion"}
    return
  }
  if { $clientVersion != $VERSION } {
    catch {close $socketID} tmpError
    if {$DEBUG} {puts "ERROR: Client connect denied - mismatched versions" }
    if {$DEBUG} {puts "CLIENT VERSION: $clientVersion" }
    if {$DEBUG} {puts "SERVER VERSION: $VERSION" }
    ClientExitClose $socketID
    return
  }
  if {$OPENSSL} {
    tls::import $socketID -server true -keyfile $KEY -certfile $PEM
    fileevent $socketID readable [list HandShake $socketID ClientCmdRcvd]
  } else {
    fileevent $socketID readable [list ClientCmdRcvd $socketID]
  } 
} 
proc HandShake { socketID cmd } {
  if {[eof $socketID]} {
    close $socketID
    ClientExitClose socketID
  } elseif { [catch {tls::handshake $socketID} results] } {
    puts "ERROR: $results"
    close $socketID
    ClientExitClose socketID
  } elseif {$results == 1} {
    puts "Handshake complete for $socketID"
    fileevent $socketID readable [list $cmd $socketID]
  }
}
proc SensorConnect { socketID IPAddr port } {
  global DEBUG
  if {$DEBUG} {puts "Connect from $IPAddr:$port $socketID"}
  # Check the client access list
  if { ![ValidateSensorAccess $IPAddr] } {
    SendSocket $socketID "Connection Refused."
    catch {close $socketID} tmpError
    if {$DEBUG} { puts "Invalid access attempt from $IPAddr" }
    return
  }
  if {$DEBUG} { puts "ALLOWED" }
  fconfigure $socketID -buffering line -blocking 0
  fileevent $socketID readable [list SensorCmdRcvd $socketID]
}
proc GetSensorID { sensorName } {
  # For now we query the DB everytime we need the sid.
  set sid [FlatDBQuery "SELECT sid FROM sensor WHERE hostname='$sensorName'"]
  return $sid
}
proc RcvSsnFile { socketID tableName fileName sensorName } {
  global DEBUG TMPDATADIR DBHOST DBPORT DBNAME DBUSER DBPASS loaderWritePipe
  set sensorID [GetSensorID $sensorName]
  if {$DEBUG} {puts "Receiving session file $fileName."}
  fconfigure $socketID -translation binary
  set DB_OUTFILE $TMPDATADIR/$fileName
  set fileID [open $DB_OUTFILE w]
  fcopy $socketID $fileID
  close $fileID
  close $socketID
  if {$sensorID == 0} {
    if {$DEBUG} {
      puts "ERROR: $sensorName is not in DB!!"
    }
    SendSystemInfoMsg sguild "ERROR: Received session file from unkown sensor - $sensorName"
    return
  }
  set inFileID [open $DB_OUTFILE r]
  set outFileID [open $DB_OUTFILE.tmp w]
  # Use i to keep track of how many lines we loaded into the database for DEBUG.
  set i 0
  # Load the entire file into memory (read $inFileID), then create a list
  # delimited by \n. Finally loop through each 'line', prepend the sensorID (sid)
  # to it, and append the new line to the tmp file.
  foreach line [split [read $inFileID] \n] {
    if {$line != ""} {puts $outFileID "$sensorID|$line"; incr i}
  }
  close $inFileID
  close $outFileID
  file delete $DB_OUTFILE

  if {$DEBUG} {puts "Loading $i cnxs from $fileName into $tableName."}
  if {$DBPASS != "" } {
    set cmd "mysql --local-infile -D $DBNAME -h $DBHOST -P $DBPORT -u $DBUSER --password=$DBPASS\
     -e \"LOAD DATA LOCAL INFILE '$DB_OUTFILE.tmp' INTO TABLE $tableName FIELDS TERMINATED\
     BY '|'\""
  } else {
    set cmd "mysql --local-infile -D $DBNAME -h $DBHOST -P $DBPORT -u $DBUSER\
     -e \"LOAD DATA LOCAL INFILE '$DB_OUTFILE.tmp' INTO TABLE $tableName FIELDS TERMINATED\
     BY '|'\""
  }
  # The loader child proc does the LOAD for us.
  puts $loaderWritePipe [list $DB_OUTFILE.tmp $cmd]
  flush $loaderWritePipe
}
proc RcvPortscanFile { socketID fileName } {
  global DEBUG TMPDATADIR DBHOST DBPORT DBNAME DBUSER DBPASS loaderWritePipe
  if {$DEBUG} {puts "Recieving portscan file $fileName."}
  fconfigure $socketID -translation binary
  set PS_OUTFILE $TMPDATADIR/$fileName
  set fileID [open $PS_OUTFILE w]
  fcopy $socketID $fileID
  close $fileID
  close $socketID
  if {$DEBUG} {puts "Loading $fileName into DB."}
  if {$DBPASS != "" } {
    set cmd "mysql --local-infile -D $DBNAME -h $DBHOST -P $DBPORT -u $DBUSER --password=$DBPASS\
     -e \"LOAD DATA LOCAL INFILE '$PS_OUTFILE' INTO TABLE portscan FIELDS TERMINATED\
     BY '|'\""
  } else {
    set cmd "mysql --local-infile -D $DBNAME -h $DBHOST -P $DBPORT -u $DBUSER\
     -e \"LOAD DATA LOCAL INFILE '$PS_OUTFILE' INTO TABLE portscan FIELDS TERMINATED\
     BY '|'\""
  }
  # The loader child proc does the LOAD for us.
  puts $loaderWritePipe [list $PS_OUTFILE $cmd]
  flush $loaderWritePipe
}

proc DiskReport { socketID fileSystem percentage } {
  global agentSensorName
  SendSystemInfoMsg $agentSensorName($socketID) "$fileSystem $percentage"
}
proc SensorAgentConnect { socketID sensorName } {
  global connectedAgents agentSocket agentSensorName
  lappend connectedAgents $sensorName
  set agentSocket($sensorName) $socketID
  set agentSensorName($socketID) $sensorName
  SendSystemInfoMsg $sensorName "Agent connected."
}
proc CleanUpDisconnectedAgent { socketID } {
  global connectedAgents agentSocket agentSensorName

  set connectedAgents [ldelete $connectedAgents $agentSensorName($socketID)]
  set sensorName $agentSensorName($socketID)
  unset agentSocket($sensorName)
  unset agentSensorName($socketID)
}
proc SensorCmdRcvd { socketID } {
  global DEBUG connectedAgents agentSensorName
  if { [eof $socketID] || [catch {gets $socketID data}] } {
    # Socket closed
    catch { close $socketID } closeError
    if {$DEBUG} { puts "Socket $socketID closed" }
    if { [info exists connectedAgents] && [info exists agentSensorName($socketID)] } {
      CleanUpDisconnectedAgent $socketID
    }
  } else {
    if {$DEBUG} { puts "Sensor Data Rcvd: $data" }
    # note that ctoken changes the string that it is operating on 
    # so instead of re-writing all of the proc to move up one in 
    # the index when looking at $data, I wrote $data to $tmpData
    # before using ctoken.  Probably should drop this and fix the
    # procs, but that can happen later
    set tmpData $data
    set sensorCmd [ctoken tmpData " "]
    # set sensorCmd [lindex $data 0]
    switch -exact -- $sensorCmd {
      RTEvent	{ EventRcvd $socketID $data }
      PSFile	{ RcvPortscanFile $socketID [lindex $data 1] }
      CONNECT   { SensorAgentConnect $socketID [lindex $data 1] }
      DiskReport { $sensorCmd $socketID [lindex $data 1] [lindex $data 2] }
      SsnFile	{ RcvSsnFile $socketID [lindex $data 1] [lindex $data 2] [lindex $data 3] }
      PING	{ puts $socketID "PONG"; flush $socketID }
      XscriptDebugMsg { $sensorCmd [lindex $data 1] [lindex $data 2] }
      RawDataFile { $sensorCmd $socketID [lindex $data 1] [lindex $data 2] }
      default	{ if {$DEBUG} {puts "Sensor Cmd Unkown ($socketID): $sensorCmd"} }
    }
  }
}

#
# RuleRequest finds rule based on message. Should change this to
# use sig ids in the future.
#
proc RuleRequest { socketID sensor message } {
  global RULESDIR DEBUG

  set RULEFOUND 0
  set ruleDir $RULESDIR/$sensor
  if { [file exists $ruleDir] } {
    foreach ruleFile [glob -nocomplain $ruleDir/*.rules] {
      if {$DEBUG} {puts "Checking $ruleFile..."}
      set ruleFileID [open $ruleFile r]
      while { [gets $ruleFileID data] >= 0 } {
        if { [string match "*$message*" $data] } {
          set RULEFOUND 1
          if {$DEBUG} {puts "Matching rule found in $ruleFile."}
          break
        }
      }
      close $ruleFileID
      if {$RULEFOUND} {break}
    }
  } else {
    set data "Could not find $ruleDir."
  }
  if {$RULEFOUND} { 
    catch {SendSocket $socketID "InsertRuleData $data"} tmpError
  } else {
    catch {SendSocket $socketID "InsertRuleData Unable to find matching rule in $ruleDir."} tmpError
  }
}
proc GetPSData { socketID timestamp srcIP MAX_PS_ROWS } {
  global DBNAME DBUSER DBPASS DBPORT DBHOST DEBUG
  if { $MAX_PS_ROWS == 0 } {
    set query\
    "SELECT * FROM portscan WHERE timestamp > '$timestamp' AND src_ip='$srcIP'"
  } else {
    set query\
    "SELECT * FROM portscan WHERE timestamp > '$timestamp' AND src_ip='$srcIP' LIMIT $MAX_PS_ROWS"
  }
  if {$DEBUG} {puts "Getting PS data: $query"}
  if {$DBPASS == ""} {
    set dbSocketID [mysqlconnect -host $DBHOST -db $DBNAME -user $DBUSER -port $DBPORT]
  } else {
    set dbSocketID [mysqlconnect -host $DBHOST -db $DBNAME -user $DBUSER -port $DBPORT -password $DBPASS]
  }
  foreach row [mysqlsel $dbSocketID "$query" -list] {
    catch {SendSocket $socketID "PSDataResults $row"} tmpError
  }
  mysqlclose $dbSocketID
  catch {SendSocket $socketID "PSDataResults DONE"} tmpError
}
proc ExecDB { socketID query } {
  global DBNAME DBUSER DBPASS DBPORT DBHOST DEBUG
    if { [lindex $query 0] == "OPTIMIZE" } {
	SendSystemInfoMsg sguild "Table Optimization beginning, please stand by"
    }
  if {$DEBUG} {puts "Sending DB Query: $query"}
  if { $DBPASS == "" } {
      set dbSocketID [mysqlconnect -host $DBHOST -db $DBNAME -user $DBUSER -port $DBPORT]
  } else {
      set dbSocketID [mysqlconnect -host $DBHOST -db $DBNAME -user $DBUSER -port $DBPORT -password $DBPASS]
  }
  if [catch {mysqlexec $dbSocketID $query} execResults] {
	catch {SendSocket $socketID "InfoMessage \{ERROR running query, perhaps you don't have permission. Error:$execResults\}"} tmpError
  } else {
      if { [lindex $query 0] == "DELETE" } {
	  catch {SendSocket $socketID "InfoMessage Query deleted $execResults rows."} tmpError
      } elseif { [lindex $query 0] == "OPTIMIZE" } {
	  catch {SendSocket $socketID "InfoMessage Database Command Completed."} tmpError
	  SendSystemInfoMsg sguild "Table Optimization Completed."
      } else { 
	  catch {SendSocket $socketID "InfoMessge Database Command Completed."} tmpError
      }
  }
  mysqlclose $dbSocketID
}
 
proc QuerydCmdRcvd { pipeID } {
  if { [eof $pipeID] || [catch {gets $pipeID data}] } {
    CleanExit
  } else {
    if [catch {SendSocket [lindex $data 0] [lrange $data 1 end]} tmpErr] { puts "$tmpErr" }
  }
}
proc ForkQueryd {} {
  global DEBUG mainWritePipe mainReadPipe
  # This pipe sends to queryd
  pipe queryReadPipe mainWritePipe
  # THis pipe lets queryd send back.
  pipe mainReadPipe queryWritePipe
  # Fork the child
  if {[set childPid [fork]] == 0 } {
    # We are the child now.
    proc mainCmdRcvd { inPipeID outPipeID } {
      global DEBUG
      fconfigure $inPipeID -buffering line
      if { [eof $inPipeID] || [catch {gets $inPipeID data}] } {
        exit
      } else {
        set dbCmd [lindex $data 3]
        set clientSocketID [lindex $data 0]
        set clientWinName [lindex $data 1]
	if {[lindex $clientWinName 0] == "REPORT"} {
	    set ClientCommand "ReportResponse"
	    set clientWinName [lindex $clientWinName 1]
	} else {
	    set ClientCommand "InsertQueryResults"
	}
        set query [lindex $data 2]
        if {$DEBUG} {puts "Sending DB Query: $query"}
        set dbSocketID [eval $dbCmd]
        if [catch {mysqlsel $dbSocketID "$query" -list} selResults] {
          puts $outPipeID "$clientSocketID InfoMessage $selResults"
        } else {
          set count 0
          foreach row $selResults {
            puts $outPipeID "$clientSocketID $ClientCommand $clientWinName $row"
            incr count
          }
          if { $ClientCommand != "ReportResponse" } {puts $outPipeID "$clientSocketID InfoMessage Query returned $count row(s)."}
        }
        puts $outPipeID "$clientSocketID $ClientCommand $clientWinName done"
        flush $outPipeID
        mysqlclose $dbSocketID
      }
    }
    fileevent $queryReadPipe readable [list mainCmdRcvd $queryReadPipe $queryWritePipe]
    if {$DEBUG} { puts "Queryd Forked" }
  }
  return $childPid
}
proc QueryDB { socketID clientWinName query } {
  global mainWritePipe
  global DBNAME DBUSER DBPASS DBPORT DBHOST DEBUG 

  # Just pass the query to queryd.
  if { $DBPASS == "" } {
    set dbCmd "mysqlconnect -host $DBHOST -db $DBNAME -user $DBUSER -port $DBPORT"
  } else {
    set dbCmd "mysqlconnect -host $DBHOST -db $DBNAME -user $DBUSER -port $DBPORT -password $DBPASS"
  }
  puts $mainWritePipe [list $socketID $clientWinName $query $dbCmd]
  flush $mainWritePipe
}
proc FlatDBQuery { query } {
  global DBNAME DBUSER DBPORT DBHOST DBPASS

  if { $DBPASS == "" } {
    set dbSocketID [mysqlconnect -host $DBHOST -db $DBNAME -user $DBUSER -port $DBPORT]
  } else {
    set dbSocketID [mysqlconnect -host $DBHOST -db $DBNAME -user $DBUSER -port $DBPORT -password $DBPASS]
  }
  set queryResults [mysqlsel $dbSocketID $query -flatlist]
  mysqlclose $dbSocketID
  return $queryResults
}
proc EventHistoryRequest { socketID winName sid cid } {
  global DBNAME DBUSER DBPORT DBHOST DBPASS
  if { $DBPASS == "" } {
    set dbSocketID [mysqlconnect -host $DBHOST -db $DBNAME -user $DBUSER -port $DBPORT]
  } else {
    set dbSocketID [mysqlconnect -host $DBHOST -db $DBNAME -user $DBUSER -port $DBPORT -password $DBPASS]
  }
  set query "SELECT history.sid, history.cid, user_info.username, history.timestamp, history.status, status.description, history.comment FROM history, user_info, status WHERE history.uid=user_info.uid AND history.status=status.status_id AND history.sid=$sid and history.cid=$cid"
  if [catch {mysqlsel $dbSocketID "$query" -list} selResults] {
    catch {SendSocket $socketID "InfoMessage $selResults"} tmpError
  } else {
    foreach row $selResults {
      catch {SendSocket $socketID "InsertHistoryResults $winName $row"} tmpError
    }
  }
  mysqlclose $dbSocketID
  catch {SendSocket $socketID "InsertHistoryResults $winName done"} tmpError
}
proc DBCommand { query } {
  global DBNAME DBUSER DBPORT DBHOST DBPASS

  if { $DBPASS == "" } {
    set dbCmd [list mysqlconnect -host $DBHOST -db $DBNAME -user $DBUSER -port $DBPORT]
  } else {
    set dbCmd [list mysqlconnect -host $DBHOST -db $DBNAME -user $DBUSER -port $DBPORT -password $DBPASS]
  }

  # Connect to the DB
  if { [ catch {eval $dbCmd} dbSocketID ] } {
    puts "ERROR Connecting to the DB: $dbSocketID"
    CleanExit
  }

  if [catch {mysqlexec $dbSocketID $query} tmpError] {
    puts "ERROR Execing DB cmd: $query"
    puts "$tmpError"
    CleanExit
  }
  catch {mysqlclose $dbSocketID}
  return 
}
proc UpdateDBStatusList { whereTmp timestamp uid status } {
  global DBNAME DBUSER DBPORT DBHOST DBPASS
  set updateString "UPDATE event SET status=$status, last_modified='$timestamp', last_uid='$uid' WHERE $whereTmp"
  if { $DBPASS == "" } {
    set dbSocketID [mysqlconnect -host $DBHOST -db $DBNAME -user $DBUSER -port $DBPORT]
  } else {
    set dbSocketID [mysqlconnect -host $DBHOST -db $DBNAME -user $DBUSER -port $DBPORT -password $DBPASS]
  }
  
  set execResults [mysqlexec $dbSocketID $updateString]
  mysqlclose $dbSocketID
  return $execResults
}
proc UpdateDBStatus { eventID timestamp uid status } {
  global DBNAME DBUSER DBPORT DBHOST DBPASS
  set sid [lindex [split $eventID .] 0]
  set cid [lindex [split $eventID .] 1]
  set updateString\
   "UPDATE event SET status=$status, last_modified='$timestamp', last_uid='$uid' WHERE sid=$sid AND cid=$cid"
  if { $DBPASS == "" } {
    set dbSocketID [mysqlconnect -host $DBHOST -db $DBNAME -user $DBUSER -port $DBPORT]
  } else {
    set dbSocketID [mysqlconnect -host $DBHOST -db $DBNAME -user $DBUSER -port $DBPORT -password $DBPASS]
  }
  set execResults [mysqlexec $dbSocketID $updateString]
  mysqlclose $dbSocketID
}
proc GetSancpFlagData { socketID sensorName xid } {
  set query\
   "SELECT src_flags, dst_flags FROM sancp INNER JOIN sensor ON sancp.sid=sensor.sid\
    WHERE sensor.hostname='$sensorName' AND sancp.sancpid=$xid" 
   set queryResults [FlatDBQuery $query]
   catch {SendSocket $socketID "InsertSancpFlags $queryResults"}
}
proc GetIPData { socketID sid cid } {
  set query\
   "SELECT INET_NTOA(src_ip), INET_NTOA(dst_ip), ip_ver, ip_hlen, ip_tos, ip_len, ip_id,\
    ip_flags, ip_off, ip_ttl, ip_csum\
   FROM event\
   WHERE sid=$sid and cid=$cid"

  set queryResults [FlatDBQuery $query]
  catch {SendSocket $socketID "InsertIPHdr $queryResults"} tmpError
}
proc GetTcpData { socketID sid cid } {
  set query\
   "SELECT tcp_seq, tcp_ack, tcp_off, tcp_res, tcp_flags, tcp_win, tcp_urp, tcp_csum\
   FROM tcphdr\
   WHERE sid=$sid and cid=$cid"
  set queryResults [FlatDBQuery $query]
  set portQuery [FlatDBQuery "SELECT src_port, dst_port FROM event WHERE sid=$sid AND cid=$cid"]
  catch {SendSocket $socketID "InsertTcpHdr $queryResults $portQuery"} tmpError
}
proc GetIcmpData { socketID sid cid } {
  set query\
   "SELECT event.icmp_type, event.icmp_code, icmphdr.icmp_csum, icmphdr.icmp_id, icmphdr.icmp_seq\
   FROM event, icmphdr\
   WHERE event.sid=icmphdr.sid AND event.cid=icmphdr.cid AND event.sid=$sid AND event.cid=$cid"

  set queryResults [FlatDBQuery $query]
  
  set query\
   "SELECT data_payload FROM data WHERE sid=$sid and cid=$cid"
  
  set plqueryResults [FlatDBQuery $query]

  catch {SendSocket $socketID "InsertIcmpHdr $queryResults $plqueryResults"} tmpError
}
proc GetPayloadData { socketID sid cid } {
  set query\
   "SELECT data_payload FROM data WHERE sid=$sid and cid=$cid"

  set queryResults [FlatDBQuery $query]
  catch {SendSocket $socketID "InsertPayloadData \{$queryResults\}"} tmpError
}
proc GetUdpData { socketID sid cid } {
  set query\
   "SELECT udp_len, udp_csum FROM udphdr WHERE sid=$sid and cid=$cid"

  set queryResults [FlatDBQuery $query]
  set portQuery [FlatDBQuery "SELECT src_port, dst_port FROM event WHERE sid=$sid AND cid=$cid"]
  catch {SendSocket $socketID "InsertUdpHdr $queryResults $portQuery"} tmpError
}


#
# ReportBuilder:  Receive multiple data requests from the client for report building
#
proc ReportBuilder { socketID type sid cid } {
    switch -exact -- $type {
	TCP {
	    set query\
		"SELECT tcphdr.tcp_seq, tcphdr.tcp_ack, tcphdr.tcp_off, tcphdr.tcp_res,\
		tcphdr.tcp_flags, tcphdr.tcp_win, tcphdr.tcp_csum, tcphdr.tcp_urp, event.src_port, event.dst_port\
		FROM tcphdr, event\
		WHERE event.sid=tcphdr.sid AND event.cid=tcphdr.cid and event.sid=$sid and event.cid=$cid"
	    QueryDB $socketID {REPORT TCP} $query
	}
	UDP {
	    set query\
		"select udphdr.udp_len, udphdr.udp_csum, event.src_port, event.dst_port\
		FROM udphdr, event\
		WHERE event.sid = udphdr.sid and event.cid=udphdr.cid and event.sid=$sid and event.cid = $cid"
	
	    QueryDB $socketID {REPORT UDP} $query
	}
        ICMP {
	    set query\
		"SELECT event.icmp_type, event.icmp_code, icmphdr.icmp_csum, icmphdr.icmp_id, icmphdr.icmp_seq, data.data_payload\
		FROM event, icmphdr, data\
		WHERE event.sid=icmphdr.sid AND event.cid=icmphdr.cid\
		AND event.sid=data.sid AND event.cid=data.cid AND event.sid=$sid AND event.cid=$cid"

	    QueryDB $socketID {REPORT ICMP} $query
	}
	PAYLOAD {
	    set query\
		"SELECT data_payload FROM data WHERE data.sid=$sid and data.cid=$cid"

	    QueryDB $socketID {REPORT PAYLOAD} $query 
	}
	PORTSCAN {
	    set MAX_PS_ROWS 200
	    set timestamp $sid
	    set srcIP $cid
	    if { $MAX_PS_ROWS == 0 } {
		set query\
		    "SELECT * FROM portscan WHERE timestamp > '$timestamp' AND src_ip='$srcIP'"
	    } else {
		set query\
		    "SELECT * FROM portscan WHERE timestamp > '$timestamp' AND src_ip='$srcIP' LIMIT $MAX_PS_ROWS"
	    }
	    QueryDB $socketID {REPORT PORTSCAN} $query
       }
       IP {
	    set query\
		"SELECT INET_NTOA(src_ip), INET_NTOA(dst_ip), ip_ver, ip_hlen, ip_tos, ip_len, ip_id,\
		ip_flags, ip_off, ip_ttl, ip_csum\
		FROM event\
		WHERE sid=$sid and cid=$cid"

	    QueryDB $socketID {REPORT IP} $query
       }
       CATCOUNT {
	    set query "SELECT  status.description , COUNT(event.status) from event, status WHERE event.status = status.status_id"
	    if { $sid != "ALL" } {
		set query "${query} AND event.sid = 3"
	    }
	    set query "${query} GROUP BY status.description"
	    set queryResults [FlatDBQuery $query]
	    catch {SendSocket $socketID "ReportResponse $type $queryResults"} tmpError
	}
	
    }
}

#
# SendSensorList: Sends a list of sensors for the end user to select from.
#
proc SendSensorList { socketID } {
  global sensorList clientMonitorSockets socketInfo sensorUsers
  set query "SELECT hostname FROM sensor WHERE active='Y'"
  set sensorList [FlatDBQuery $query]
  if { $sensorList == "" } { 
    # No sensors in the DB yet
    set fullSensorList "0none0"
  } else {
    # create a list of sensors and a list of the sensors users
    foreach sensor $sensorList {
      if { ![info exists sensorUsers($sensor)] || $sensorUsers($sensor) == "" } {
        lappend fullSensorList "$sensor unmonitored"
      } else {
        lappend fullSensorList [list $sensor $sensorUsers($sensor)]
      }
    }
  }
  puts $socketID "SensorList $fullSensorList"
}
#
# MonitorSensors: Sends current events to client. Adds client to clientList
#                 In the future sensorList will contain a list of sensors, for
#                 now the client gets everything.
#
proc MonitorSensors { socketID sensorList } {
  global DEBUG clientList clientMonitorSockets connectedAgents socketInfo sensorUsers
  if {$DEBUG} {puts "$socketID added to clientList"}
  lappend clientList $socketID
  foreach sensorName $sensorList {
    lappend clientMonitorSockets($sensorName) $socketID
    lappend sensorUsers($sensorName) [lindex $socketInfo($socketID) 2]
  }
  SendSystemInfoMsg sguild "User [lindex $socketInfo($socketID) 2] is monitoring sensors: $sensorList"
  SendCurrentEvents $socketID
  #if { [info exists connectedAgents] } {
  #  SendSystemInfoMsg sguild "Connected sensors - $connectedAgents"
  #}
}

proc SendEscalatedEvents { socketID } {
  global escalateIDList escalateArray
  if [info exists escalateIDList] {
    foreach escalateID $escalateIDList {
      catch {SendSocket $socketID "InsertEscalatedEvent $escalateArray($escalateID)"} tmpError
    }
  }
}

#
# SendCurrentEvents: Sends newly connected clients the current event list
#
proc SendCurrentEvents { socketID } {
  global eventIDArray eventIDList DEBUG clientMonitorSockets eventIDCountArray
  
  if { [info exists eventIDList] && [llength $eventIDList] > 0 } {
    foreach eventID $eventIDList {
      set sensorName [lindex $eventIDArray($eventID) 3]
      if { [info exists clientMonitorSockets($sensorName)] } {
        if { [lsearch -exact $clientMonitorSockets($sensorName) $socketID] >= 0} {
          if {$DEBUG} { puts "Sending client $socketID: InsertEvent $eventIDArray($eventID)" }
          catch {SendSocket $socketID "InsertEvent $eventIDArray($eventID) $eventIDCountArray($eventID)"} tmpError
        }
      }
    }
  }
}
proc CreateDB { DBNAME } {
  global dbSocketID
  puts -nonewline "The database $DBNAME does not exist. Create it (\[y\]/n)?: "
  flush stdout
  set answer [gets stdin]
  if { $answer == "" } { set answer y }
  if { ![regexp {^[yY]} $answer] } { return 0 }
  set fileName "./sql_scripts/create_sguildb.sql"
  puts -nonewline "Path to create_sguildb.sql \[$fileName\]: "
  flush stdout
  set answer [gets stdin]
  if { $answer != "" } { set fileName $answer }
  if { ! [file exists $fileName] } {
    puts "File does not exist: $fileName"
    return 0
  }
  puts -nonewline "Creating the DB $DBNAME..."
  if [ catch {mysqlexec $dbSocketID "CREATE DATABASE $DBNAME"} createDBError] {
    puts $createDBError
    return 0
  }
  mysqluse $dbSocketID $DBNAME
  puts "Okay."
  if [catch {set fileID [open $fileName r]} openFileError] {
    puts $openFileError
    return 0
  }
  puts -nonewline "Creating the structure for $DBNAME: "
  foreach line [split [read $fileID] \n] {
    puts -nonewline "."
    if { $line != "" && ![regexp {^--} $line]} {
      #puts "LINE: $line"
      if { [regexp {(^.*);\s*$} $line match data] } {
        lappend mysqlCmd $data
        #puts "CMD: [join $mysqlCmd]"
        mysqlexec $dbSocketID [join $mysqlCmd]
        set mysqlCmd ""
      } else {
        lappend mysqlCmd $line
      }
    }
  }
  close $fileID
  puts "Done."
  return 1
}
proc Daemonize {} {
  global PID_FILE DEBUG
  set DEBUG 0
  set childPID [fork]
  # Parent exits.
  if { $childPID == 0 } { exit }
  id process group set
  if {[fork]} {exit 0}
  set PID [id process]
  if { ![info exists PID_FILE] } { set PID_FILE "/var/run/sguild.pid" }
  set PID_DIR [file dirname $PID_FILE]
  if { ![file exists $PID_DIR] || ![file isdirectory $PID_DIR] || ![file writable $PID_DIR] } {
    puts "ERROR: Directory $PID_DIR does not exists or is not writable."
    puts "Process ID will not be written to file."
  } else {
    set pidFileID [open $PID_FILE w]
    puts $pidFileID $PID
    close $pidFileID
  }
}
######################## END PROCS ##############################

###################### MAIN #####################################

set validSockets ""

# Load mysql support.
if [catch {package require mysqltcl} mysqltclVersion] {
  puts "ERROR: The mysqltcl extension does NOT appear to be installed on this sysem."
  puts "Download it at http://www.xdobry.de/mysqltcl/"
  CleanExit
}
# Load extended tcl
if [catch {package require Tclx} tclxVersion] {
  puts "ERROR: The tclx extension does NOT appear to be installed on this sysem."
  puts "Extended tcl (tclx) is available as a port/package for most linux and BSD systems."
  CleanExit
}
# Load sha1 from tcllib
if [catch {package require sha1} sha1Version] {
  puts "ERROR: The sha1 package does NOT appear to be installed on this sysem."
  puts "The sha1 package is part of the tcllib extension. A port/package is available for most linux and BSD systems."
  CleanExit
}
# Load base64 from tcllib
if [catch {package require base64} base64Version] {
  puts "ERROR: The base64 package does NOT appear to be installed on this sysem."
  puts "The base64 package is part of the tcllib extension. A port/package is available for most linux and BSD systems."
  CleanExit
}
# reset the random
random seed

# GetOpts
set state flag
foreach arg $argv {
  switch -- $state {
    flag {
      switch -glob -- $arg {
        -- { set state flag }
        -h { DisplayUsage $argv0}
        -c { set state conf }
        -a { set state autocat }
        -g { set state gquery }
        -u { set state users_file }
        -D { set DAEMON_CONF_OVERRIDE 1 }
        -P { set state pid_file }
        -O { set state openssl }
        -o { set OPENSSL 1 }
        -C { set state certs }
        -A { set state accessfile }
        -adduser { set state adduser }
        -deluser { set state deluser }
        default { DisplayUsage $argv0 }
      }
    }
    conf { set CONF_FILE $arg; set state flag }
    autocat { set AUTOCAT_FILE $arg; set state flag }
    gquery { set GLOBAL_QRY_FILE $arg; set state flag }
    users_file { set USERS_FILE $arg; set state flag }
    pid_file { set PID_FILE $arg; set state flag }
    openssl { set OPENSSL 1; set TLS_PATH $arg; set state flag }
    certs { set CERTS_PATH $arg; set state flag }
    adduser { set ADDUSER 1; set userName $arg; set state flag }
    deluser { set DELUSER 1; set userName $arg; set state flag }
    accessfile { set ACCESS_FILE $arg; set state flag }
    default { DisplayUsage $argv0 }
  }
}

# Check openssl requirements if enabled
if { [info exists OPENSSL] && $OPENSSL } {
  set VERSION "$VERSION OPENSSL ENABLED"
  # Need a path to the tls libs
  if { [info exists TLS_PATH] } {
    if [catch {load $TLS_PATH} tlsError] {
      puts "ERROR: Unable to load tls libs ($TLS_PATH): $tlsError"
      DisplayUsage $argv0
    }
  }
  package require tls
  # Check for certs
  if {![info exists CERTS_PATH]} {
    set CERTS_PATH /etc/sguild/certs
  }
  if {![file exists $CERTS_PATH] || ![file isdirectory $CERTS_PATH]} {
    puts "ERROR: $CERTS_PATH does not exist or is not a directory"
    DisplayUsage $argv0
  }
  # Need sguild.key and sguild.pem
  set PEM [file join $CERTS_PATH sguild.pem]
  set KEY [file join $CERTS_PATH sguild.key]
  global KEY
  puts "KEY is $KEY"
  if {![file exists $PEM] || ![file readable $PEM] } {
    puts "ERROR: $PEM does not exist or is not readable"
    DisplayUsage $argv0
  }
  if {![file exists $KEY] || ![file readable $KEY] } {
    puts "ERROR: $KEY does not exist or is not readable"
    DisplayUsage $argv0
  }
  # If we get this far we should be good.
} else {
  set OPENSSL 0
  set VERSION "$VERSION OPENSSL DISABLED"
}

if { ![info exists CONF_FILE] } {
  # No conf file specified check the defaults
  if { [file exists /etc/sguild/sguild.conf] } {
    set CONF_FILE /etc/sguild/sguild.conf
  } elseif { [file exists ./sguild.conf] } {
    set CONF_FILE ./sguild.conf
  } else {
    puts "Couldn't determine where the sguil config file is"
    puts "Looked for ./sguild.conf and /etc/sguild/sguild.conf."
    DisplayUsage $argv0
  }
}
set i 0
if { [info exists CONF_FILE] } {
  # Parse the config file. Currently the only option is to 
  # create a variable using 'set varName value' 
  for_file line $CONF_FILE {
    incr i
    if { ![regexp ^# $line] && ![regexp ^$ $line] } {
      if { [CheckLineFormat $line] } {
        if { [catch {eval $line} evalError] } {
          puts "Error at line $i in $CONF_FILE: $line"
          CleanExit
        }
      } else {
        puts "Error at line $i in $CONF_FILE: $line"
        CleanExit
      }
    }
  }
} else {
  DisplayUsage $argv0
}
# Check for a valid USERS file
if { ![info exists USERS_FILE] } {
  # No users file was specified. Go with the defaults
  if { [file exists /etc/sguild/sguild.users] } {
    set USERS_FILE "/etc/sguild/sguild.users"
  } elseif { [file exists ./sguild.users] } {
    set USERS_FILE "./sguild.users"
  } else {
    if { [info exists ADDUSER] && $ADDUSER } {
      CreateUsersFile "/etc/sguild/sguil.users"
    } else {
      puts "ERROR: Could not find a sguild.users file."
      puts "       Checked in ./ and /etc/sguild/"
      DisplayUsage $argv0
    }
  }
} else {
  if { ![file exists $USERS_FILE] } {
    if { [info exists ADDUSER] && $ADDUSER } {
      CreateUsersFile $USERS_FILE
    } else {
      puts "ERROR: $USERS_FILE does not exist"
      DisplayUsage $argv0
    }
  }
}
# Called in to add a user only
if { [info exists ADDUSER] && $ADDUSER } {
  AddUser $userName $USERS_FILE
  CleanExit
}
# Called in to delte a user only
if { [info exists DELUSER] && $DELUSER} {
  DelUser $userName $USERS_FILE
  CleanExit
}
# Load accessfile
if { ![info exists ACCESS_FILE] } {
  # Check the defaults
  if { [file exists /etc/sguild/sguild.access] } {
    set ACCESS_FILE "/etc/sguild/sguild.access"
  } elseif { [file exists ./sguild.access] } {
    set ACCESS_FILE "./sguild.access"
  } else {
    puts "ERROR: No sguild.access file found."
    DisplayUsage $argv0   
  }
}
if { [file exists $ACCESS_FILE] } {
  LoadAccessFile $ACCESS_FILE
}
# Load auto cat config
if { ![info exists AUTOCAT_FILE] } {
   if { [file exists /etc/sguild/autocat.conf] } {
     set AUTOCAT_FILE "/etc/sguild/autocat.conf"
   } else {
     set AUTOCAT_FILE "./autocat.conf"
   }
}
if { [file exists $AUTOCAT_FILE] } {
  LoadAutoCatFile $AUTOCAT_FILE
}
# Load global queries.
if { ![info exists GLOBAL_QRY_FILE] } {
  if { [file exists /etc/sguild/sguild.queries] } {
    set GLOBAL_QRY_FILE "/etc/sguild/sguild.queries"
  } else {
    set GLOBAL_QRY_FILE "./sguild.queries"
  }
}
if { [file exists $GLOBAL_QRY_FILE] } {
  LoadGlobalQueries $GLOBAL_QRY_FILE
} else {
  set GLOBAL_QRY_LIST none
}

# Deamon
if {[info exists DAEMON_CONF_OVERRIDE] && $DAEMON_CONF_OVERRIDE} { set DAEMON 1}
if {$DAEMON} { Daemonize }

# Fork a child to load PS/SSN info
set childPid [ForkLoader]
if { $childPid == 0 } { vwait LOADER }
lappend FORKD_PIDS $childPid

# Fork a child to handle queries
set childPid [ForkQueryd]
if { $childPid == 0 } { vwait QUERYD }
lappend FORKD_PIDS $childPid
fileevent $mainReadPipe readable [list QuerydCmdRcvd $mainReadPipe]
fconfigure $mainReadPipe -buffering line

# Get archived alerts from the DB
if { $DBPASS == "" } {
  set connectCmd "-host $DBHOST -user $DBUSER -port $DBPORT"
  #set dbSocketID [mysqlconnect -host $DBHOST -user $DBUSER -port $DBPORT]
} else {
  #set dbSocketID [mysqlconnect -host $DBHOST -user $DBUSER -port $DBPORT -password $DBPASS]
  set connectCmd "-host $DBHOST -user $DBUSER -port $DBPORT -password $DBPASS"
}
if [catch {eval mysqlconnect $connectCmd} dbSocketID] {
  puts "ERROR: Unable to connect to $DBHOST on $DBPORT: Make sure mysql is running."
  puts "$dbSocketID"
  CleanExit
}
# See if the DB we want to use exists
if { [catch {mysqluse $dbSocketID $DBNAME} noDBError] } {
  puts "Error: $noDBError"
  # Create the DB or die.
  if {![CreateDB $DBNAME]} { CleanExit }
}
# Make sure we have a compatible DB version
set currentDBVer [FlatDBQuery "SELECT version FROM version"]
if { [lsearch $DB_VERSION $currentDBVer] < 0 } {
  puts "ERROR: Incompatable DB schema.\nRequired Version: $DB_VERSION\nInstalled Version: $currentDBVer"
  puts "Check the server/sql_scripts directory of the src that came with sguild for scripts to help you upgrade"
  CleanExit
}

# If emailing of events is enabled, we need to make sure the libs are installed.
if { [info exists EMAIL_EVENTS] && $EMAIL_EVENTS } {
  package require mime
  package require smtp
} else {
  # Just in case the var doesn't get set in sguild.conf
  set EMAIL_EVENTS 0
}

# Set the AUTOID before we get events.
set AUTOID [GetUserID auto]
# Initialize some vars
set eventIDList ""

if {$DEBUG} {
  puts "Querying DB for archived events..."
  puts "SELECT event.status, event.priority, event.class, sensor.hostname, event.timestamp, event.sid, event.cid, event.signature, INET_NTOA(event.src_ip), INET_NTOA(event.dst_ip), event.ip_proto, event.src_port, event.dst_port FROM event, sensor WHERE event.sid=sensor.sid AND event.status=0 ORDER BY event.timestamp ASC"
}
foreach row [mysqlsel $dbSocketID "SELECT event.status, event.priority, event.class, sensor.hostname, event.timestamp, event.sid, event.cid, event.signature, INET_NTOA(event.src_ip), INET_NTOA(event.dst_ip), event.ip_proto, event.src_port, event.dst_port FROM event, sensor WHERE event.sid=sensor.sid AND event.status=0 ORDER BY event.timestamp ASC" -list] {
  if {$DEBUG} {puts "Archived Alert: $row"}
  if { ![array exists acRules] || ![AutoCat $row] } {
    set matchAID [CorrelateEvent [lindex $row 8] [lindex $row 7]]
    if { $matchAID == 0 } {
      AddEventToEventArray $row
    } else {
      # Add event to parents list
      lappend correlatedEventArray($matchAID) $row
      # Bump the parents count
      incr eventIDCountArray($matchAID)
    }
  }
}
if {$DEBUG} {
  puts "Querying DB for escalated events..."
  puts "SELECT event.status, event.priority, event.class, sensor.hostname, event.timestamp, event.sid, event.cid, event.signature, INET_NTOA(event.src_ip), INET_NTOA(event.dst_ip), event.ip_proto, event.src_port, event.dst_port FROM event, sensor WHERE event.sid=sensor.sid AND event.status=2 ORDER BY event.timestamp ASC"
}
foreach row [mysqlsel $dbSocketID "SELECT event.status, event.priority, event.class, sensor.hostname, event.timestamp, event.sid, event.cid, event.signature, INET_NTOA(event.src_ip), INET_NTOA(event.dst_ip), event.ip_proto, event.src_port, event.dst_port FROM event, sensor WHERE event.sid=sensor.sid AND event.status=2 ORDER BY event.timestamp ASC" -list] {
  if {$DEBUG} {puts "Escalated Event: $row"}
  set escalatedEventID "[lindex $row 5].[lindex $row 6]"
  lappend escalateIDList $escalatedEventID
  set escalateArray($escalatedEventID) $row
}
# Get DB info (table names and column info)
if {$DEBUG} { puts "Retrieving DB info..." }
set tableNameList [mysqlinfo $dbSocketID tables]
foreach tableName $tableNameList {
  set tableArray($tableName) [mysqlcol $dbSocketID $tableName {name type length}]
}
mysqlclose $dbSocketID
set sensorQuery "SELECT hostname FROM sensor"
set sensorList [FlatDBQuery $sensorQuery]


# Open a socket for clients to connect to
if { [info exists BIND_CLIENT_IP_ADDR] && $BIND_CLIENT_IP_ADDR != "" } {
  set clientSocketCmd "socket -server ClientConnect -myaddr $BIND_CLIENT_IP_ADDR $SERVERPORT"
} else {
  set clientSocketCmd "socket -server ClientConnect $SERVERPORT"
}
if [catch {eval $clientSocketCmd} serverSocket] {
  puts "ERROR: Couldn't open client socket: $serverSocket"
  CleanExit
}
# Open a socket for sensors to connect to
if { [info exists BIND_SENSOR_IP_ADDR] && $BIND_SENSOR_IP_ADDR != "" } {
  set sensorSocketCmd "socket -server SensorConnect -myaddr $BIND_SENSOR_IP_ADDR $SENSORPORT"
} else {
  set sensorSocketCmd "socket -server SensorConnect $SENSORPORT"
}
if [catch {eval $sensorSocketCmd} sensorSocket] {
  puts "ERROR: Couldn't open sensor socket: $sensorSocket"
  CleanExit
}
puts "Sguild Initialized."

signal trap {HUP} HupTrapped
signal trap {QUIT TERM} CleanExit

# Infinate wait
vwait FOREVER

