# -*- coding: binary -*-
module Rex

###
#
# This class provides os-specific functionality
#
###
module Compat

STD_INPUT_HANDLE  = -10
STD_OUTPUT_HANDLE = -11
STD_ERROR_HANDLE  = -12

GENERIC_READ    = 0x80000000
GENERIC_WRITE   = 0x40000000
GENERIC_EXECUTE = 0x20000000

FILE_SHARE_READ  = 0x00000001
FILE_SHARE_WRITE = 0x00000002
OPEN_EXISTING    = 0x00000003

ENABLE_LINE_INPUT = 2
ENABLE_ECHO_INPUT = 4
ENABLE_PROCESSED_INPUT = 1



#
# Platform detection
#

@@is_windows = @@is_cygwin = @@is_macosx = @@is_linux = @@is_bsdi = @@is_freebsd = @@is_netbsd = @@is_openbsd = @@is_java = @@is_android = false
@@loaded_win32api  = false
@@loaded_tempfile  = false
@@loaded_fileutils = false

def self.is_windows
  return @@is_windows if @@is_windows
  # Additionally detect x64-mingw-ucrt - https://rubyinstaller.org/2022/11/27/rubyinstaller-3.1.3-1-3.0.5-1-and-2.7.7-1-released.html
  @@is_windows = (RUBY_PLATFORM =~ /mswin(32|64)|mingw(32|64|\-ucrt)/) ? true : false
end

def self.is_cygwin
  return @@is_cygwin if @@is_cygwin
  @@is_cygwin = (RUBY_PLATFORM =~ /cygwin/) ? true : false
end

def self.is_macosx
  return @@is_macosx if @@is_macosx
  @@is_macosx = (RUBY_PLATFORM =~ /darwin/) ? true : false
end

def self.is_linux
  return @@is_linux if @@is_linux
  @@is_linux = (RUBY_PLATFORM =~ /linux/) ? true : false
end

def self.is_bsdi
  return @@is_bsdi if @@is_bsdi
  @@is_bsdi = (RUBY_PLATFORM =~ /bsdi/i) ? true : false
end

def self.is_netbsd
  return @@is_netbsd if @@is_netbsd
  @@is_netbsd = (RUBY_PLATFORM =~ /netbsd/) ? true : false
end

def self.is_freebsd
  return @@is_freebsd if @@is_freebsd
  @@is_freebsd = (RUBY_PLATFORM =~ /freebsd/) ? true : false
end

def self.is_openbsd
  return @@is_openbsd if @@is_openbsd
  @@is_openbsd = (RUBY_PLATFORM =~ /openbsd/) ? true : false
end

def self.is_java
  return @@is_java if @@is_java
  @@is_java = (RUBY_PLATFORM =~ /java/) ? true : false
end

def self.is_android
  return @@is_android if @@is_android
  @@is_android = (RUBY_PLATFORM =~ /android/) ? true : false
end

def self.is_wow64
  return false if not is_windows
  is64 = false
  begin
    buff = "\x00" * 4
    Win32API.new("kernel32","IsWow64Process",['L','P'],'L').call(-1, buff)
    is64 = (buff.unpack("V")[0]) == 1 ? true : false
  rescue ::Exception
  end
  is64
end

def self.cygwin_to_win32(path)
  if(path !~ /^\/cygdrive/)
    return ::IO.popen("cygpath -w #{path}", "rb").read.strip
  end
  dir = path.split("/")
  dir.shift
  dir.shift
  dir[0] = dir[0] + ":"
  dir.join("\\")
end

def self.open_file(url='')
  case RUBY_PLATFORM
  when /cygwin/
    path = self.cygwin_to_win32(url)
    system(["cmd", "cmd"], "/c", "explorer", path)
  else
    self.open_browser(url)
  end
end

def self.open_browser(url='http://google.com/')
  case RUBY_PLATFORM
  when /cygwin/
    if(url[0,1] == "/")
      self.open_file(url)
    end
    return if not @@loaded_win32api
    Win32API.new("shell32.dll", "ShellExecute", ["PPPPPL"], "L").call(nil, "open", url, nil, nil, 0)
  when /mswin32|mingw/
    return if not @@loaded_win32api
    Win32API.new("shell32.dll", "ShellExecute", ["PPPPPL"], "L").call(nil, "open", url, nil, nil, 0)
  when /darwin/
    system("open #{url}")
  when /android/
    url = "file://#{url}" if url.to_s.start_with?('/')
    system("am start --user 0 -a android.intent.action.VIEW -d #{url}")
  else
    # Search through the PATH variable (if it exists) and chose a browser
    # We are making an assumption about the nature of "PATH" so tread lightly
    #
    # If DISPLAY is not set, most unix graphical tools will not work.
    if ENV['PATH'] && ENV['DISPLAY']
      # "xdg-open" is more general than "sensible-browser" and can be useful for lots of
      # file types -- text files, pcaps, or URLs. It's nearly always
      # going to use the application the user is expecting. If we're not
      # on something Debian-based, fall back to likely browsers.
      ['xdg-open', 'sensible-browser', 'firefox', 'firefox-bin', 'opera', 'konqueror', 'chromium-browser'].each do |browser|
        ENV['PATH'].split(':').each do |path|
          # Does the browser exists?
          if File.exist?("#{path}/#{browser}")
            system("#{browser} #{url} &")
            return
          end
        end
      end
    end
  end
end

def self.open_webrtc_browser(url='http://google.com/')
  case RUBY_PLATFORM
  when /mswin2|mingw|cygwin/
      paths = [
        "Google\\Chrome\\Application\\chrome.exe",
        "Mozilla Firefox\\firefox.exe",
        "Opera\\launcher.exe"
      ]

      prog_files = ENV['ProgramFiles']
      paths = paths.map { |p| "#{prog_files}\\#{p}" }

      # Old chrome path
      app_data = ENV['APPDATA']
      paths << "#{app_data}\\Google\\Chrome\\Application\\chrome.exe"

      paths.each do |path|
        if File.exist?(path)
          args = (path =~ /chrome\.exe/) ? "--allow-file-access-from-files" : ""
          system("\"#{path}\" #{args} \"#{url}\"")
          return true
        end
      end

  when /darwin/
    ['Google Chrome.app', 'Firefox.app'].each do |browser|
      browser_path = "/Applications/#{browser}"
      if File.directory?(browser_path)
        args = (browser_path =~ /Chrome/) ? "--args --allow-file-access-from-files" : ""

        system("open #{url} -a \"#{browser_path}\" #{args} &")
        return true
      end
    end
  when /android/
    url = "file://#{url}" if url.to_s.start_with?('/')
    system("am start --user 0 -a android.intent.action.VIEW -d #{url}")
  else
    # If DISPLAY is not set, most unix graphical tools will not work.
    if ENV['PATH'] && ENV['DISPLAY']
      [ENV['MSF_BROWSER_BIN'], 'google-chrome', 'chrome', 'chromium', 'firefox', 'opera'].compact.each do |browser|
        ENV['PATH'].split(':').each do |path|
          browser_path = "#{path}/#{browser}"
          if File.exist?(browser_path)
            args = (browser_path =~ /Chrome/) ? "--allow-file-access-from-files" : ""
            system("#{browser_path} #{args} #{url} &")
            return true
          end
        end
      end
    end
  end

  false
end

def self.open_email(addr)
  case RUBY_PLATFORM
  when /mswin32|cygwin/
    return if not @@loaded_win32api
    Win32API.new("shell32.dll", "ShellExecute", ["PPPPPL"], "L").call(nil, "open", "mailto:"+addr, nil, nil, 0)
  when /darwin/
    system("open mailto:#{addr}")
  when /android/
    system("am start --user 0 -a android.intent.action.VIEW -d mailto:#{addr}")
  else
    # ?
  end
end

def self.play_sound(path)
  case RUBY_PLATFORM
  when /cygwin/
    path = self.cygwin_to_win32(path)
    return if not @@loaded_win32api
    Win32API.new("winmm.dll", "sndPlaySoundA", ["SI"], "I").call(path, 0x20000)
  when /mswin32/
    return if not @@loaded_win32api
    Win32API.new("winmm.dll", "sndPlaySoundA", ["SI"], "I").call(path, 0x20000)
  when /darwin/
    system("afplay #{path} >/dev/null 2>&1")
  when /android/
    system("termux-open #{path}")
  else
    system("aplay #{path} >/dev/null 2>&1")
  end
end

def self.getenv(var)
  if (is_windows and @@loaded_win32api)
    f = Win32API.new("kernel32", "GetEnvironmentVariable", ["P", "P", "I"], "I")
    buff = "\x00" * 16384
    sz = f.call(var, buff, buff.length)
    return nil if sz == 0
    buff[0,sz]
  else
    ENV[var]
  end
end

def self.setenv(var,val)
  if (is_windows and @@loaded_win32api)
    f = Win32API.new("kernel32", "SetEnvironmentVariable", ["P", "P"], "I")
    f.call(var, val + "\x00")
  else
    ENV[var]= val
  end
end


#
# Obtain the path to our interpreter
#
def self.win32_ruby_path
  return nil if ! (is_windows and @@loaded_win32api)
  gmh = Win32API.new("kernel32", "GetModuleHandle", ["P"], "L")
  gmf = Win32API.new("kernel32", "GetModuleFileName", ["LPL"], "L")
  mod = gmh.call(nil)
  inf = "\x00" * 1024
  gmf.call(mod, inf, 1024)
  inf.unpack("Z*")[0]
end

#
# Call WinExec (equiv to system("cmd &"))
#
def self.win32_winexec(cmd)
  return nil if ! (is_windows and @@loaded_win32api)
  exe = Win32API.new("kernel32", "WinExec", ["PL"], "L")
  exe.call(cmd, 0)
end

#
# Verify the Console2 environment
#
def self.win32_console2_verify
  return nil if ! (is_windows and @@loaded_win32api)
  buf = "\x00" * 512
  out = Win32API.new("kernel32", "GetStdHandle", ["L"], "L").call(STD_OUTPUT_HANDLE)
  res = Win32API.new("kernel32","GetConsoleTitle", ["PL"], "L").call(buf, buf.length-1) rescue 0
  ( res > 0 and buf.index("Console2 command").nil? ) ? false : true
end

#
# Expand a 8.3 path to a full path
#
def self.win32_expand_path(path)
  return nil if ! (is_windows and @@loaded_win32api)
  glp = Win32API.new('kernel32', 'GetLongPathName', 'PPL', 'L')
  buf = "\x00" * 260
  len = glp.call(path, buf, buf.length)
  buf[0, len]
end

#
# Platform independent socket pair
#
def self.pipe

  if (! is_windows())
    # Standard pipes should be fine
    return ::IO.pipe
  end

  # Create a socket connection for Windows
  serv = nil
  port = 1024

  while (! serv and port < 65535)
    begin
      serv = TCPServer.new('127.0.0.1', (port += 1))
    rescue ::Exception
    end
  end

  pipe1 = TCPSocket.new('127.0.0.1', port)

  # Accept the forked child
  pipe2 = serv.accept

  # Shutdown the server
  serv.close

  return [pipe1, pipe2]
end

#
# Copy a file to a temporary path
#

def self.temp_copy(path)
  raise RuntimeError,"missing Tempfile" if not @@loaded_tempfile
  fd = File.open(path, "rb")
  tp = Tempfile.new("msftemp")
  tp.binmode
  tp.write(fd.read(File.size(path)))
  tp.close
  fd.close
  tp
end

#
# Delete an opened temporary file
#

def self.temp_delete(tp)
  raise RuntimeError,"missing FileUtils" if not @@loaded_fileutils
  begin
    FileUtils.rm(tp.path)
  rescue
  end
end


#
# Initialization
#

if(is_windows or is_cygwin)
  begin
    require "Win32API"
    @@loaded_win32api = true
  rescue ::Exception
  end
end

begin
  require "tempfile"
  @@loaded_tempfile = true
rescue ::Exception
end

begin
  require "fileutils"
  @@loaded_fileutils = true
rescue ::Exception
end



end
end

