;; Custom winpcap for nmap
;; Recognizes the options (case sensitive):
;;   /S              silent install
;;   /NPFSTARTUP=NO  start NPF now and at startup (only has effect with /S)

;; Started by Doug Hoyte, April 2006

;; Eddie Bell
;; Updated to 4.0, June 2007
;; Updated to 4.01, July 2007
;; Updated to 4.02, November 2007

;; Rob Nicholls
;; Updated to 4.1.1, October 2009
;; Updated to 4.1.2, July 2010

SetCompressor /SOLID /FINAL lzma

;--------------------------------
;Include Modern UI

  !include "MUI.nsh"
  !include "FileFunc.nsh"

;--------------------------------
;General

; The name of the installer
Name "WinPcap (Nmap) 4.1.2"

; The file to write
OutFile "winpcap-nmap-4.12.exe"

RequestExecutionLevel admin

; These leave either "1" or "0" in $0.
Function is64bit
  System::Call "kernel32::GetCurrentProcess() i .s"
  System::Call "kernel32::IsWow64Process(i s, *i .r0)"
FunctionEnd
Function un.is64bit
  System::Call "kernel32::GetCurrentProcess() i .s"
  System::Call "kernel32::IsWow64Process(i s, *i .r0)"
FunctionEnd

VIProductVersion "4.1.0.2001"
VIAddVersionKey /LANG=1033 "FileVersion" "4.1.0.2001"
VIAddVersionKey /LANG=1033 "ProductName" "WinPcap"
VIAddVersionKey /LANG=1033 "FileDescription" "WinPcap 4.1.2 installer"
VIAddVersionKey /LANG=1033 "LegalCopyright" ""

;--------------------------------
; Windows API Definitions

!define SC_MANAGER_ALL_ACCESS           0x3F
!define SERVICE_ALL_ACCESS              0xF01FF

; Service Types
!define SERVICE_FILE_SYSTEM_DRIVER      0x00000002
!define SERVICE_KERNEL_DRIVER           0x00000001
!define SERVICE_WIN32_OWN_PROCESS       0x00000010
!define SERVICE_WIN32_SHARE_PROCESS     0x00000020
!define SERVICE_INTERACTIVE_PROCESS     0x00000100

; Service start options
!define SERVICE_AUTO_START              0x00000002
!define SERVICE_BOOT_START              0x00000000
!define SERVICE_DEMAND_START            0x00000003
!define SERVICE_DISABLED                0x00000004
!define SERVICE_SYSTEM_START            0x00000001

; Service Error control
!define SERVICE_ERROR_CRITICAL          0x00000003
!define SERVICE_ERROR_IGNORE            0x00000000
!define SERVICE_ERROR_NORMAL            0x00000001
!define SERVICE_ERROR_SEVERE            0x00000002

; Service Control Options
!define SERVICE_CONTROL_STOP            0x00000001
!define SERVICE_CONTROL_PAUSE           0x00000002



;--------------------------------
;Interface Settings

  !define MUI_ABORTWARNING

;--------------------------------
;Pages

!insertmacro MUI_PAGE_LICENSE "LICENSE"
; Don't let user choose where to install the files. WinPcap doesn't let people, and it's one less thing for us to worry about.
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
Page custom optionsPage doOptions
Page custom finalPage doFinal

;--------------------------------
;Languages

  !insertmacro MUI_LANGUAGE "English"

;--------------------------------
;Reserves

ReserveFile "options.ini"
ReserveFile "final.ini"
!insertmacro MUI_RESERVEFILE_INSTALLOPTIONS

;--------------------------------

!insertmacro GetParameters
!insertmacro GetOptions

; This function is called on startup. IfSilent checks
; if the flag /S was specified. If so, it sets the installer
; to run in "silent mode" which displays no windows and accepts
; all defaults.

; We also check if there is a previously installed winpcap
; on this system. If it's the same as the version we're installing,
; abort the install. If not, prompt the user about whether to
; replace it or not.

Function .onInit
  !insertmacro MUI_INSTALLOPTIONS_EXTRACT "options.ini"
  !insertmacro MUI_INSTALLOPTIONS_EXTRACT "final.ini"

  var /GLOBAL inst_ver
  var /GLOBAL my_ver
  var /GLOBAL npf_startup
  StrCpy $my_ver "4.1.0.2001"
  StrCpy $npf_startup "YES"

  ; Always use the requested /D= $INSTDIR if given.
  StrCmp $INSTDIR "" "" instdir_nochange
  ; On 64-bit Windows, $PROGRAMFILES is "C:\Program Files (x86)" and
  ; $PROGRAMFILES64 is "C:\Program Files". We want "C:\Program Files"
  ; on 32-bit or 64-bit.
  StrCpy $INSTDIR "$PROGRAMFILES\WinPcap"
  Call is64bit
  StrCmp $0 "0" instdir_nochange
  StrCpy $INSTDIR "$PROGRAMFILES64\WinPcap"
  instdir_nochange:

  ${GetParameters} $R0
  ClearErrors
  ${GetOptions} $R0 "/NPFSTARTUP=" $npf_startup

  IfSilent do_silent no_silent

  do_silent:
    SetSilent silent
    IfFileExists "$SYSDIR\wpcap.dll" silent_checks
    return
    silent_checks:
      ; check for the presence of Nmap's custom WinPcapInst registry key:
      ReadRegStr $0 "HKLM" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\WinPcapInst" "InstalledBy"
      StrCmp $0 "Nmap" silent_uninstall winpcap_installedby_keys_not_present

      winpcap_installedby_keys_not_present:
      ; check for the presence of WinPcapInst's UninstallString
      ; and manually cleanup registry entries to avoid running
      ; the GUI uninstaller and assume our installer will overwrite
      ; the files. Needs to be checked in case someone (force)
      ; installs WinPcap over the top of our installation
      ReadRegStr $0 "HKLM" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\WinPcapInst" "UninstallString"
      StrCmp $0 "" winpcap_keys_not_present

      DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\WinPcapInst"

      ReadRegStr $0 "HKLM" "Software\WinPcap" ""
      StrCmp $0 "" winpcap_keys_not_present

      Delete $0\rpcapd.exe
      Delete $0\LICENSE
      Delete $0\uninstall.exe
      ; Official 4.1 installer creates an install.log
      Delete $0\install.log
      RMDir "$0"
      DeleteRegKey HKLM "Software\WinPcap"

      ; because we've deleted their uninstaller, skip the next
      ; registry key check (we'll still need to overwrite stuff)
      Goto winpcap-nmap_keys_not_present

      winpcap_keys_not_present:

      ; if our old registry key is present then assume all is well
      ; (we got this far so the official WinPcap wasn't installed)
      ; and use our uninstaller to (magically) silently uninstall
      ; everything cleanly and avoid having to overwrite files
      ReadRegStr $0 "HKLM" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\winpcap-nmap" "UninstallString"
      StrCmp $0 "" winpcap-nmap_keys_not_present silent_uninstall

      winpcap-nmap_keys_not_present:

      ; setoverwrite on to try and avoid any problems when trying to install the files
      ; wpcap.dll is still present at this point, but unclear where it came from
      SetOverwrite on

      ; try to ensure that npf has been stopped before we install/overwrite files
      ExecWait '"net stop npf"'

      return

      silent_uninstall:
        ; Our InstalledBy string is present, UninstallString should have quotes and uninstall.exe location
        ; and this file should support a silent uninstall by passing /S to it.
        ; we could read QuietUninstallString, but this should be exactly the same as UninstallString with /S on the end.
        ReadRegStr $0 "HKLM" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\WinPcapInst" "UninstallString"
        ExecWait '$0 /S _?=$INSTDIR'
      return

  no_silent:
    IfFileExists "$SYSDIR\wpcap.dll" do_version_check
    return

  do_version_check:

    GetDllVersion "$SYSDIR\wpcap.dll" $R0 $R1
    IntOp $R2 $R0 / 0x00010000
    IntOp $R3 $R0 & 0x0000FFFF
    IntOp $R4 $R1 / 0x00010000
    IntOp $R5 $R1 & 0x0000FFFF
    StrCpy $inst_ver "$R2.$R3.$R4.$R5"

    StrCmp $inst_ver $my_ver same_ver

    MessageBox MB_YESNO|MB_ICONQUESTION "WinPcap version $inst_ver exists on this system. Replace with version $my_ver?" IDYES try_uninstallers
    quit

  same_ver:
    MessageBox MB_OK "Skipping WinPcap installation since version $inst_ver already exists on this system.  Uninstall that version first if you wish to force install."
    quit

  try_uninstallers:

    ; check for UninstallString and use that in preference (should already have double quotes and uninstall.exe)
    ReadRegStr $0 "HKLM" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\WinPcapInst" "UninstallString"
    StrCmp $0 "" no_uninstallstring
    IfFileExists "$0" uninstaller_exists no_uninstallstring
    uninstaller_exists:
    ExecWait '$0 _?=$INSTDIR'
    return

    no_uninstallstring:
    ; didn't find an UninstallString, check for our old UninstallString and if uninstall.exe exists:
    ReadRegStr $0 "HKLM" "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\winpcap-nmap" "UninstallString"
    StrCmp $0 "" still_no_uninstallstring
    IfFileExists "$0" old_uninstaller_exists still_no_uninstallstring
    old_uninstaller_exists:
    MessageBox MB_OK "Using our old UninstallString, file exists"
    ExecWait '$0 _?=$INSTDIR'
    return

    still_no_uninstallstring:
    ; still didn't find anything, try looking for an uninstall.exe file at:
      ReadRegStr $0 "HKLM" "Software\WinPcap" ""
    ; Strip any surrounding double quotes from around the install string,
    ; as WinPcap hasn't used quotes in the past, but our old installers did.
    ; Check the first and last character for safety!
    StrCpy $1 $0 1
    StrCmp $1 "$\"" maybestripquotes nostrip
    maybestripquotes:
    StrLen $1 $0
    IntOp $1 $1 - 1
    StrCpy $1 $0 1 $1
    StrCmp $1 "$\"" stripquotes nostrip
    stripquotes:
    StrCpy $0 $0 -1 1
    nostrip:
    IfFileExists "$0\uninstall.exe" run_last_uninstaller no_uninstall_exe
    run_last_uninstaller:
    ExecWait '"$0\Uninstall.exe" _?=$INSTDIR'
    no_uninstall_exe:
    ; give up now, we've tried our hardest to determine a valid uninstaller!
    return

FunctionEnd

Function optionsPage
  !insertmacro MUI_HEADER_TEXT "WinPcap Options" ""
  !insertmacro MUI_INSTALLOPTIONS_DISPLAY "options.ini"
FunctionEnd

Function doOptions
  ReadINIStr $0 "$PLUGINSDIR\options.ini" "Field 1" "State"
  StrCmp $0 "0" do_options_next
  WriteRegDWORD HKLM "SYSTEM\CurrentControlSet\Services\NPF" "Start" 2
  do_options_next:
  ReadINIStr $0 "$PLUGINSDIR\options.ini" "Field 2" "State"
  StrCmp $0 "0" do_options_end
  nsExec::Exec "net start npf"
  do_options_end:
FunctionEnd

Function finalPage
  ; diplay a page saying everything's finished
  !insertmacro MUI_HEADER_TEXT "Finished" "Thank you for installing WinPcap"
  !insertmacro MUI_INSTALLOPTIONS_DISPLAY "final.ini"
FunctionEnd

Function doFinal
 ; don't need to do anything
FunctionEnd

Function registerServiceAPI
  ; delete the npf service to avoid an error message later if it already exists
  System::Call 'advapi32::OpenSCManagerA(,,i ${SC_MANAGER_ALL_ACCESS})i.r0'
  System::Call 'advapi32::OpenServiceA(i r0,t "npf", i ${SERVICE_ALL_ACCESS}) i.r1'
  System::Call 'advapi32::DeleteService(i r1) i.r6'
  System::Call 'advapi32::CloseServiceHandle(i r1) n'
  System::Call 'advapi32::CloseServiceHandle(i r0) n'
  ; create the new npf service
  System::Call 'advapi32::OpenSCManagerA(,,i ${SC_MANAGER_ALL_ACCESS})i.R0'
  System::Call 'advapi32::CreateServiceA(i R0,t "npf",t "NetGroup Packet Filter Driver",i ${SERVICE_ALL_ACCESS},i ${SERVICE_KERNEL_DRIVER}, i ${SERVICE_DEMAND_START},i ${SERVICE_ERROR_NORMAL}, t "system32\drivers\npf.sys",,,,,) i.r1'
  StrCmp $1 "0" register_fail register_success
  register_fail:
    DetailPrint "Failed to create the npf service"
    IfSilent close_register_handle register_fail_messagebox
    register_fail_messagebox:
      MessageBox MB_OK "Failed to create the npf service. Please try installing WinPcap again, or use the official WinPcap installer from www.winpcap.org"
    Goto close_register_handle
  register_success:
    DetailPrint "The npf service was successfully created"
  close_register_handle:
  System::Call 'advapi32::CloseServiceHandle(i R0) n'
FunctionEnd

Function un.registerServiceAPI
  System::Call 'advapi32::OpenSCManagerA(,,i ${SC_MANAGER_ALL_ACCESS})i.r0'
  System::Call 'advapi32::OpenServiceA(i r0,t "npf", i ${SERVICE_ALL_ACCESS}) i.r1'
  System::Call 'advapi32::DeleteService(i r1) i.r6'
  StrCmp $6 "0" unregister_fail unregister_success
  unregister_fail:
    DetailPrint "Failed to delete the npf service"
    Goto close_unregister_handle
  unregister_success:
    DetailPrint "The npf service was successfully deleted"
  close_unregister_handle:
  System::Call 'advapi32::CloseServiceHandle(i r1) n'
  System::Call 'advapi32::CloseServiceHandle(i r0) n'
FunctionEnd

Function autoStartWinPcap
    WriteRegDWORD HKLM "SYSTEM\CurrentControlSet\Services\NPF" "Start" 2
    nsExec::Exec "net start npf"
FunctionEnd


;--------------------------------
; The stuff to install
Section "WinPcap" SecWinPcap

  ; stop the service, in case it's still registered, so files can be
  ; safely overwritten and the service can be deleted.
  nsExec::Exec "net stop npf"

  ; NB: We may need to introduce a check here to ensure that NPF
  ; has been stopped before we continue, otherwise we Sleep for a
  ; while and try the check again. This might help prevent any race
  ; conditions during a silent install (and potentially during the
  ; slower GUI installation.

  ; These x86 files are automatically redirected to the right place on x64
  SetOutPath $SYSDIR
  File pthreadVC.dll
  File wpcap.dll

  ; Check windows version
  ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion" CurrentVersion
  DetailPrint "Windows CurrentVersion: $R0"
  StrCpy $R0 $R0 2
  StrCmp $R0 '6.' vista_files

  File nt5\x86\Packet.dll
  Goto install

  vista_files:
    File vista\x86\Packet.dll

  install:
    Call is64bit
    StrCmp $0 "0" install_32bit install_64bit

    ; Note, NSIS states: "You should always quote the path to make sure spaces
    ; in the path will not disrupt Windows to find the uninstaller."
    ; See: http://nsis.sourceforge.net/Add_uninstall_information_to_Add/Remove_Programs
    ; This matches (most) Windows installations. Rather inconsistently,
    ; DisplayIcon doesn't usually have quotes (even on Microsoft installations) and
    ; HKLM Software\PackageName doesn't usually have quotes either.

    install_32bit:
      SetOutPath $INSTDIR
      File rpcapd.exe
      File LICENSE
      WriteUninstaller "$INSTDIR\uninstall.exe"
      DetailPrint "Installing x86 driver"
      SetOutPath $SYSDIR\drivers
      File npf.sys ; x86 NT5/NT6 version
      WriteRegStr HKLM "Software\WinPcap" "" "$INSTDIR"
      WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\WinPcapInst" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
      WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\WinPcapInst" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S"
      WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\WinPcapInst" "DisplayIcon" "$INSTDIR\uninstall.exe"
      Goto npfdone

    install_64bit:
      SetOutPath $INSTDIR
      File rpcapd.exe
      File LICENSE
      WriteUninstaller "$INSTDIR\uninstall.exe"
      DetailPrint "Installing x64 driver"
      SetOutPath $SYSDIR\drivers
      ; disable Wow64FsRedirection
      System::Call kernel32::Wow64EnableWow64FsRedirection(i0)
      File x64\npf.sys ; x64 NT5/NT6 version
      ; The x86 versions of wpcap.dll and packet.dll are
      ; installed into the right place further above.
      ; install the 64-bit version of wpcap.dll into System32
      SetOutPath $SYSDIR
      File x64\wpcap.dll ; x64 NT5/NT6 version
      ; install the 64-bit version of packet.dll into System32
      ; check for vista, otherwise install the NT5 version (for XP and 2003)
      StrCpy $R0 $R0 2
      StrCmp $R0 '6.' vista_x64_packet
      File nt5\x64\Packet.dll ; x64 XP/2003 version
      Goto nt5_x64_packet_done
      vista_x64_packet:
      File vista\x64\Packet.dll ; x64 Vista version
      nt5_x64_packet_done:
      WriteRegStr HKLM "Software\WinPcap" "" "$INSTDIR"
      ; re-enable Wow64FsRedirection
      System::Call kernel32::Wow64EnableWow64FsRedirection(i1)
      WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\WinPcapInst" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
      WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\WinPcapInst" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S"
      WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\WinPcapInst" "DisplayIcon" "$INSTDIR\uninstall.exe"

    npfdone:

    ; register the driver as a system service using Windows API calls
    ; this will work on Windows 2000 (that lacks sc.exe) and higher
    Call registerServiceAPI

    ; Create the default NPF startup setting of 3 (SERVICE_DEMAND_START)
    WriteRegDWORD HKLM "SYSTEM\CurrentControlSet\Services\NPF" "Start" 3

    ; automatically start the service if performing a silent install, unless
    ; /NPFSTARTUP=NO was given.
    IfSilent 0 skip_auto_start
    StrCmp $npf_startup "NO" skip_auto_start
      Call autoStartWinPcap
    skip_auto_start:

    ; Write the rest of the uninstall keys for Windows

    WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\WinPcapInst" "DisplayName" "WinPcap 4.1.2"
    WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\WinPcapInst" "DisplayVersion" "4.1.0.2001"
    WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\WinPcapInst" "Publisher" "CACE Technologies"
    WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\WinPcapInst" "URLInfoAbout" "http://www.cacetech.com"
    WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\WinPcapInst" "URLUpdateInfo" "http://www.winpcap.org"
    WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\WinPcapInst" "VersionMajor" "4"
    WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\WinPcapInst" "VersionMinor" "1"
    WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\WinPcapInst" "InstalledBy" "Nmap"
    WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\WinPcapInst" "NoModify" 1
    WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\WinPcapInst" "NoRepair" 1

  ; delete our  legacy winpcap-nmap keys if they still exist (e.g. official 4.0.2 force installed over our 4.0.2):
  DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\winpcap-nmap"

SectionEnd ; end the section


;--------------------------------
;Uninstaller Section

Section "Uninstall"

  ; stop npf before we delete the service from the registry
  nsExec::Exec "net stop npf"
  ; unregister the driver as a system service using Windows API calls, so it works on Windows 2000
  Call un.registerServiceAPI

  ; delete our winpcap-nmap and any WinPcapInst registry keys
  DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\winpcap-nmap"
  DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\WinPcapInst"
  DeleteRegKey HKLM "Software\WinPcap"

  Delete $INSTDIR\rpcapd.exe
  Delete $INSTDIR\LICENSE
  Delete $INSTDIR\uninstall.exe

  ; This deletes the x86 files from SysWOW64 if we're on x64.
  Delete $SYSDIR\Packet.dll
  Delete $SYSDIR\pthreadVC.dll
  Delete $SYSDIR\wpcap.dll

  ; check for x64, delete npf.sys file from system32\drivers
  Call un.is64bit
  StrCmp $0 "0" del32bitnpf del64bitnpf
  del64bitnpf:
  ; disable Wow64FsRedirection
  System::Call kernel32::Wow64EnableWow64FsRedirection(i0)

  Delete $SYSDIR\drivers\npf.sys
  ; Also delete the x64 files in System32
  Delete $SYSDIR\wpcap.dll
  Delete $SYSDIR\Packet.dll

  ; re-enable Wow64FsRedirection
  System::Call kernel32::Wow64EnableWow64FsRedirection(i1)
  Goto npfdeleted
  del32bitnpf:

  Delete $SYSDIR\drivers\npf.sys

  npfdeleted:

  RMDir "$INSTDIR"

SectionEnd