unit Unit1;

{
  CPHack v0.1.0 by Eddy L O Jansson / Released under the GPL.
  (my first real Delphi App, so no flames please :-)

  History
  ===========================================================================
  0.0.1              eloj  Preview #1
  0.0.2              eloj  Preview #2
  0.0.3              eloj  Internal
  0.1.0  2000-03-11  eloj  Last compile before first public release

  ToDo
  ===========================================================================
   * Do a nice 'free' of allocated memory on exit.
   * Fix the dictionary attacks to always be 'smart'. <sigh>
}

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ComCtrls, Menus,
  INIFiles, Clipbrd, ExtCtrls, ShellAPI, WinSock,
  CRC32, unitURLEntry, LexiconClass, HashStr; { my units }

const
 VERSIONSTRING: String[8]='0.1.0';  

type
  TForm1 = class(TForm)
    MainMenu1: TMainMenu;
    File1: TMenuItem;
    Exit1: TMenuItem;
    N1: TMenuItem;
    Open1: TMenuItem;
    OpenDialog1: TOpenDialog;
    PageControl1: TPageControl;
    TabSheet1: TTabSheet;
    UserView: TListView;
    TabSheet2: TTabSheet;
    NewsList: TListView;
    Bevel1: TBevel;
    OpenNOTlist1: TMenuItem;
    TabSheet3: TTabSheet;
    GroupCat: TGroupBox;
    CatBox0: TCheckBox;
    CatBox1: TCheckBox;
    CatBox2: TCheckBox;
    CatBox3: TCheckBox;
    CatBox4: TCheckBox;
    CatBox7: TCheckBox;
    CatBox6: TCheckBox;
    CatBox5: TCheckBox;
    CatBox8: TCheckBox;
    CatBox9: TCheckBox;
    CatBox10: TCheckBox;
    CatBox11: TCheckBox;
    CatBox12: TCheckBox;
    CatBox13: TCheckBox;
    CatBox14: TCheckBox;
    CatBox15: TCheckBox;
    TabSheet4: TTabSheet;
    IPView: TTreeView;
    IPAliasPopup: TPopupMenu;
    GoTo1: TMenuItem;
    Lookup1: TMenuItem;
    LookupAll1: TMenuItem;
    Exporthosts1: TMenuItem;
    N2: TMenuItem;
    Importhosts1: TMenuItem;
    SaveDialog1: TSaveDialog;
    Lookuprootnodes1: TMenuItem;
    N3: TMenuItem;
    Generatereport1: TMenuItem;
    InfoLabel1: TLabel;
    InfoLabel2: TLabel;
    TabSheet5: TTabSheet;
    ConfigBox: TGroupBox;
    CfgDefaultPathEdit: TEdit;
    CfgDefaultPathLbl: TLabel;
    CfgLexiconLbl: TLabel;
    CfgLexiconEdit: TEdit;
    CfgReCheckNoAnswer: TCheckBox;
    CfgDefaultPathButton: TButton;
    CfgLexiconButton: TButton;
    CfgSaveButton: TButton;
    CfgLoadButton: TButton;
    CfgPrefixLabel: TLabel;
    CfgPrefixEdit: TEdit;
    CfgPrefixButton: TButton;
    CfgSuffixEdit: TEdit;
    CfgSuffixButton: TButton;
    CfgSuffixLbl: TLabel;
    URLView: TTreeView;
    URLPopup: TPopupMenu;
    URLOpen: TMenuItem;
    URLLookupIP: TMenuItem;
    URLLookupBranch: TMenuItem;
    URLLexiconURL: TMenuItem;
    URLLexiconBranch: TMenuItem;
    CFGSuffixEnabled: TCheckBox;
    CfgPrefixEnabled: TCheckBox;
    ProgressBar: TProgressBar;
    Bevel2: TBevel;
    TheCancelButton: TButton;
    ProgressLabel: TLabel;
    URLGroupCat: TGroupBox;
    Cat2Box0: TCheckBox;
    Cat2Box1: TCheckBox;
    Cat2Box2: TCheckBox;
    Cat2Box3: TCheckBox;
    Cat2Box4: TCheckBox;
    Cat2Box7: TCheckBox;
    Cat2Box6: TCheckBox;
    Cat2Box5: TCheckBox;
    Cat2Box8: TCheckBox;
    Cat2Box9: TCheckBox;
    Cat2Box10: TCheckBox;
    Cat2Box11: TCheckBox;
    Cat2Box12: TCheckBox;
    Cat2Box13: TCheckBox;
    Cat2Box14: TCheckBox;
    Cat2Box15: TCheckBox;
    CfgExcludeURLs: TCheckBox;
    CfgExcludeIPAliases: TCheckBox;
    CfgExcludeNewsgroups: TCheckBox;
    URLLexiconWholeTree: TMenuItem;
    Exportdictionary1: TMenuItem;
    URLLookupforward1: TMenuItem;
    CFGLockURLs: TCheckBox;
    ExportURLhashes1: TMenuItem;
    URLStatbox: TGroupBox;
    URLStatLbl1: TLabel;
    URLStatBar: TProgressBar;
    URLGotonextfullURL1: TMenuItem;
    ExportunresolvedIPs1: TMenuItem;
    CFGSkipNoAnswerOnImport: TCheckBox;
    Bevel4: TBevel;
    InfoHeader: TStaticText;
    MyHomepage: TStaticText;
    N4: TMenuItem;
    Smartdictionaryattack1: TMenuItem;
    SearchEdit: TEdit;
    SearchButton: TButton;
    CFGExclURLNotLookup: TCheckBox;
    CFGExclURLHashes: TCheckBox;
    Locatemaskedas1: TMenuItem;
    Intolerance1: TMenuItem;
    ViolenceProfanity1: TMenuItem;
    PartialNudity1: TMenuItem;
    Fullnudity1: TMenuItem;
    SexualactsText1: TMenuItem;
    GrossdepictionsText1: TMenuItem;
    SatanicCult1: TMenuItem;
    DrugsDrugculture1: TMenuItem;
    MilitantExtremist1: TMenuItem;
    Sexeducation1: TMenuItem;
    QuestionableIllegalGambling1: TMenuItem;
    Alcoholtobacco1: TMenuItem;
    Reserved41: TMenuItem;
    Reserved31: TMenuItem;
    Reserved21: TMenuItem;
    Reserved11: TMenuItem;

    procedure Exit1Click(Sender: TObject);
    procedure Open1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure OpenNOTlist1Click(Sender: TObject);
    procedure NewsListSelectItem(Sender: TObject; Item: TListItem;
      Selected: Boolean);
    procedure IPViewMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure Lookup1Click(Sender: TObject);
    procedure LookupAll1Click(Sender: TObject);
    procedure Importhosts1Click(Sender: TObject);
    procedure Exporthosts1Click(Sender: TObject);
    procedure Lookuprootnodes1Click(Sender: TObject);
    procedure Generatereport1Click(Sender: TObject);
    procedure CfgSaveButtonClick(Sender: TObject);
    procedure CfgLoadButtonClick(Sender: TObject);
    procedure CfgDefaultPathButtonClick(Sender: TObject);
    procedure CfgLexiconButtonClick(Sender: TObject);
    procedure CfgPrefixButtonClick(Sender: TObject);
    procedure CfgSuffixButtonClick(Sender: TObject);
    procedure GoTo1Click(Sender: TObject);
    procedure URLOpenClick(Sender: TObject);
    procedure URLViewMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure URLLexiconURLClick(Sender: TObject);
    procedure URLViewChange(Sender: TObject; Node: TTreeNode);
    procedure URLLexiconBranchClick(Sender: TObject);
    procedure URLLexiconWholeTreeClick(Sender: TObject);
    procedure Exportdictionary1Click(Sender: TObject);
    procedure TheCancelButtonClick(Sender: TObject);
    procedure URLLookupIPClick(Sender: TObject);
    procedure URLGotonextfullURL1Click(Sender: TObject);
    procedure ExportunresolvedIPs1Click(Sender: TObject);
    procedure ExportURLhashes1Click(Sender: TObject);
    procedure URLLookupBranchClick(Sender: TObject);
    procedure URLLookupforward1Click(Sender: TObject);
    procedure MyHomepageClick(Sender: TObject);
    procedure Smartdictionaryattack1Click(Sender: TObject);
    procedure SearchButtonClick(Sender: TObject);
    procedure SearchEditKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure LocateCatByTag(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

const

 NODE_IS_NET = 0;
 NODE_IS_IP  = 1;
 NODE_IS_URL = 2;
 NODE_IS_UNKNOWN = 255;

 Categories :Array[0..$F] of String[34]=(
  'Violence / Profanity',
  'Partial Nudity',
  'Full Nudity',
  'Sexual Acts / Text',
  'Gross Depictions / Text',
  'Intolerance',
  'Satanic or Cult',
  'Drugs / Drug Culture',
  'Militant / Extremist',
  'Sex Education',
  'Questionable / Illegal & Gambling',
  'Alcohol & Tobacco',
  'Reserved 4',
  'Reserved 3',
  'Reserved 2',
  'Reserved 1');


(* This answers the question: how many freely-chosen bits do I include
 * when I ask for a crc collision with input length (index)? *)
 freebits: Array[0..11] of Byte=(0,0,0,0,1,4,10,16,22,28,34,40);

(* This says where each forced bit goes *)
 bitsforced: Array[0..31] of Byte=(0,1,2,3,4,6,
   8,9,10,11,12,14,16,17,18,19,20,22,24,25,26,27,28,30,
   32,33,34,35,38,40,41,42);

(* This says where each free bit goes *)
 bitsfree: Array[0..39] of Byte=(36,43,44,46,48,49,50,51,52,54,
   56,57,58,59,60,62,64,65,66,67,68,70,72,73,74,75,76,78,80,81,
   82,83,84,86,88,89,90,91,92,94);

(* The portion of the inverted matrix corresponding to the CRC bits *)
 crcmatrix: Array[0..31] of Cardinal=(
   $9BF7B4FE,$10CEBBDB,$3EC28E73,$E516F5B2,
   $3EB07172,$AC6CB91B,$2344667F,$25ECE58C,
   $D24109C4,$501CB10A,$97761211,$0A2EF700,
   $0C806D13,$55AE3901,$4C147270,$DAC3C857,
   $384B8A54,$F7583CAD,$A1DA1DC4,$0028BBDC,
   $B5BB7FE3,$99610C1A,$1FC446C4,$8DE0FF05,
   $01D3D128,$64FAC9B2,$3BC5E604,$E564A85C,
   $ADEB84A5,$CFCDBB2B,$3E7D9F68,$A102B971);

(* The portion of the inverted matrix corresponding to the free bits *)
 freematrix: Array[0..39] of Cardinal=(
   $0CBFC054,$AEAB35B2,$315B20B2,$1F113696,
   $6DA65FB4,$08F3CFCD,$C0E8FCF1,$D928FA77,
   $58C085F6,$55F7A6A4,$726948CB,$BEE706A6,
   $DE9BCF28,$539FADD8,$A5D7713D,$A6B4900F,
   $3CA9547B,$C98AC9B5,$AF52FA18,$60098F5B,
   $142D2C51,$706AA085,$46494250,$54026BCE,
   $EBE4D0A3,$673646B9,$945A22D6,$7C5347FB,
   $C61C9B99,$97780ADB,$7E9DB1AE,$88C43E39,
   $55CEBFB3,$5C81ADC9,$0F3DD57C,$3D44BCF3,
   $0383F8DD,$73F38757,$A8F2D5CF,$2922BEA9);

(* Matrix columns to take into account the canonicalization *)
 lengthmatrix: Array[0..11] of Cardinal=(
   $84741063,$C5273406,$E5A222DF,$9941CB2B,
   $D9EBE522,$CB93A8AF,$962E3D2D,$90029144,
   $5B298B04,$575F1D8A,$78EE4BEC,$47B6B86A);


type
 PTArrayByte = ^TArrayByte;
 TArrayByte = Array[0..MaxLongInt-1] of Byte;

 PTNOTHeader = ^TNOTHeader;
 TNOTHeader = Packed Record
  Filetype       :Word;
  HeaderSize     :Word;
  HeaderID       :Array[0..1] of Char;
  unknown1,
  unknown2,
  unknown3       :Word;
  TblEntries     :SmallInt;
 end;

 PTNOTHeaderEntry = ^TNOTHeaderEntry;
 TNOTHeaderEntry = Packed Record
  TblType        :Word;
  Offset         :LongInt;
  Size           :LongInt;
 end;

 TNewsgroupEntry = Record { internal only }
  Len           :Byte;
  Cat           :Word;
  ng            :String;
 end;

var
  Form1: TForm1;

  CfgINIFilename  :String;
  CurrNOTTbl      :Byte;

  F               :File;
  INIBuf          :PTArrayByte;
  INIBufSize      :Integer;

  NOTBuf          :PTArrayByte;
  NOTBufSize      :Integer;

  NOTHeader       :PTNOTHeader;
  NOTHeaderEntry  :PTNOTHeaderEntry;

  Users,
  Passwords,
  Newsgroups      :TStrings;

  StatisticsTotalURLHashes        :LongInt;

implementation

{$R *.DFM}

Function St(const I :LongInt): String;
var
 s:      String[10];
begin
 Str(I,S);
 St:=S;
end;

Function Va(const s: string): longint;
var
 code:   Integer;
 vas:    Longint;
begin
 Val(s,vas,code);
 Va:=vas
end;

Function LZ(tal: longint;antal: byte): String;
var
 temp:   String[20];

begin
 Str(tal,Temp);
 While length(temp)<antal do temp:='0'+temp;
 lz:=temp;
end;

Function GetProgramPath(const pstr: String): String;
var
 S       :String;
begin
 S:=pstr;
 while (length(s)>1) and (s[length(S)]<>'\') do Delete(S,Length(S),1);
 GetProgramPath:=S;
end;

Function ror(i: byte): byte; Assembler;
asm
 mov al,i
 ror al,1
end;

{ Straigt conversion of Matthew Skala's original code }
Function BreakHash(const hash1,hash2: Cardinal): String;
var
 found            :Boolean;
 res              :String;

 gothash1,
 gothash2         :Cardinal;
 length,bflip,i   :Integer;
 freeb            :Array[0..5] of Char;
 plaintext        :Array[0..11] of Char;

 Function GETBIT(const S: Array of Char;b :Byte): Byte;
 begin
  GetBit:=((ord(S[(b shr 3)]) shr (b and 7))) and 1;
 end;

 Procedure FLIPBITR(var S: Array of Char;b :Byte);
 begin
  if (length-1-(b shr 3))>=0 then S[length-1-(b shr 3)]:=char(ord((S[length-1-(b shr 3)])) xor (1 shl (b and 7)));
 end;

 Procedure FLIPBIT(var S: Array of Char;b :Byte);
 begin
  S[(b shr 3)]:=char(ord(S[(b shr 3)]) xor (1 shl (b and 7)));
 end;

 Procedure reverse_crc(const crc,length: Longint;var sin,sout: Array of Char);
 var
  bits   :Cardinal;
  i      :LongInt;
 begin
 { correct for output length }
 bits:=lengthmatrix[length-1];

 { XOR in the CRC }
 for i:=0 to 31 do
  if (crc AND (1 SHL i))<>0 then
   bits:=bits XOR crcmatrix[i];

 { XOR in the free bits }
 for i:=0 to freebits[length-1]-1 do
   if GETBIT(sin,i)<>0 then bits:=bits XOR freematrix[i];

 { Set up output }
 for i:=0 to length-1 do sout[i]:=#$20;

 { output forced bits }
 for i:=0 to 31 do
   if (bits AND (1 SHL i))<>0 then FLIPBITR(sout,bitsforced[i]);

 { output free bits }
 for i:=0 to freebits[length-1]-1 do
   if GETBIT(sin,i)<>0 then FLIPBITR(sout,bitsfree[i]);
 end;

 Procedure cp_hash(const s: Array of Char;const length: Integer;var h1,h2: cardinal);
 var
  i       :integer;
  ch      :Byte;
 begin
 h1:=0;
 h2:=0;
 for i:=0 to length-1 do
 begin
  ch:=ord(s[i]) or $20;
  h1:=(h1 shr 8) xor crctable[(h1 and $FF) xor ch];
  h2:=(((h2 shl 5) or (h2 shr $1B))+ch)-h1;
 end;
 end;

begin
 res:='';
 found:=false;
 for length:=1 to 8 do
 begin
  for i:=0 to 5 do freeb[i]:=#0;
  bflip:=0;
  repeat
   reverse_crc(hash1,length,freeb,plaintext);
   cp_hash(plaintext,length,gothash1,gothash2);
   if (hash1=gothash1) and (hash2=gothash2) then
   begin
    Setlength(res,length);
    for i:=0 to length-1 do res[i+1]:=plaintext[i];
    found:=true;
    break;
   end;
   bflip:=0;
   while GETBIT(freeb,bflip)<>0 do
   begin
    FLIPBIT(freeb,bflip);
    Inc(bflip);
   end;
   FLIPBIT(freeb,bflip);
  until (bflip>=freebits[length-1]);

  if found then break;
 end;
 BreakHash:=res;
end;

Procedure SaveConfiguration(const fn: String);
var
 DelphiIni  :TIniFile;

begin
 DelphiIni:=TIniFile.Create(fn);
 DelphiIni.WriteString('cphack','DefaultPath',Form1.CfgDefaultPathEdit.Text);
 DelphiIni.WriteString('cphack','Lexicon',Form1.CfgLexiconEdit.Text);
 DelphiIni.WriteString('cphack','PrefixFile',Form1.CfgPrefixEdit.Text);
 DelphiIni.WriteString('cphack','SuffixFile',Form1.CfgSuffixEdit.Text);

 DelphiIni.WriteBool('cphack','SuffixEnabled',Form1.CfgSuffixEnabled.Checked);
 DelphiIni.WriteBool('cphack','PrefixEnabled',Form1.CfgPrefixEnabled.Checked);

 DelphiIni.WriteBool('cphack','ExcludeNGs',Form1.CfgExcludeNewsgroups.Checked);
 DelphiIni.WriteBool('cphack','ExcludeIPAliases',Form1.CfgExcludeIPAliases.Checked);
 DelphiIni.WriteBool('cphack','ExcludeURLs',Form1.CfgExcludeURLs.Checked);

 DelphiIni.WriteBool('cphack','ExcludeNotRevIP',Form1.CFGExclURLNotLookup.Checked);
 DelphiIni.WriteBool('cphack','ExcludeNotRevHash',Form1.CFGExclURLHashes.Checked);

 DelphiIni.WriteBool('cphack','ReCheckNoAns',Form1.CfgReCheckNoAnswer.Checked);
 DelphiIni.WriteBool('cphack','LockFoundURLs',Form1.CfgLockURLs.Checked);
 DelphiIni.WriteBool('cphack','SkipNoAnswerImport',Form1.CFGSkipNoAnswerOnImport.Checked);

 Form1.ProgressLabel.Caption:='Configuration saved.';
 Form1.ProgressLabel.Refresh;

 DelphiIni.Destroy;
end;

Procedure LoadConfiguration(const fn: String);
var
 DelphiIni  :TIniFile;

begin
 DelphiIni:=TIniFile.Create(fn);
 Form1.CfgDefaultPathEdit.Text:=DelphiIni.ReadString('cphack','DefaultPath',Form1.CfgDefaultPathEdit.Text);
 Form1.CfgLexiconEdit.Text:=    DelphiIni.ReadString('cphack','Lexicon',Form1.CfgLexiconEdit.Text);
 Form1.CfgPrefixEdit.Text:=     DelphiIni.ReadString('cphack','PrefixFile',Form1.CfgPrefixEdit.Text);
 Form1.CfgSuffixEdit.Text:=     DelphiIni.ReadString('cphack','SuffixFile',Form1.CfgSuffixEdit.Text);

 Form1.CfgSuffixEnabled.Checked:=DelphiIni.ReadBool('cphack','SuffixEnabled',Form1.CfgSuffixEnabled.Checked);
 Form1.CfgPrefixEnabled.Checked:=DelphiIni.ReadBool('cphack','PrefixEnabled',Form1.CfgPrefixEnabled.Checked);

 Form1.CfgExcludeNewsgroups.Checked:=DelphiIni.ReadBool('cphack','ExcludeNGs',Form1.CfgExcludeNewsgroups.Checked);
 Form1.CfgExcludeIPAliases.Checked:=DelphiIni.ReadBool('cphack','ExcludeIPAliases',Form1.CfgExcludeIPAliases.Checked);
 Form1.CfgExcludeURLs.Checked:=DelphiIni.ReadBool('cphack','ExcludeURLs',Form1.CfgExcludeURLs.Checked);

 Form1.CFGExclURLNotLookup.Checked:=DelphiIni.ReadBool('cphack','ExcludeNotRevIP',Form1.CFGExclURLNotLookup.Checked);
 Form1.CFGExclURLHashes.Checked:=DelphiIni.ReadBool('cphack','ExcludeNotRevHash',Form1.CFGExclURLHashes.Checked);

 Form1.CfgLockURLs.Checked:=DelphiIni.ReadBool('cphack','LockFoundURLs',Form1.CfgLockURLs.Checked);
 Form1.CfgReCheckNoAnswer.Checked:=DelphiIni.ReadBool('cphack','ReCheckNoAns',Form1.CfgReCheckNoAnswer.Checked);
 Form1.CFGSkipNoAnswerOnImport.Checked:=DelphiIni.ReadBool('cphack','SkipNoAnswerImport',Form1.CFGSkipNoAnswerOnImport.Checked);

 Form1.ProgressLabel.Caption:='Configuration loaded.';

 DelphiIni.Destroy;
 end;

Procedure Decrypt(var Buf: PTArrayByte;const BufSize: LongInt);
var
 key     :Byte;
 i,j     :LongInt;
begin
 key:=BufSize AND $FF;
 for j:=0 to 1 do
  for i:=0 to BufSize-1 do
  begin
   key:=ror(key);
   buf^[i]:=buf^[i] xor key;
   key:=buf^[i];
  end;
end;

Function Array8ToStr(const BinBuf: PTArrayByte): String;
var
 S       :String[8];
 i       :Byte;
begin
 for i:=1 to 8 do S[i]:=Char(BinBuf^[i-1]);
 S[0]:=#8;
 Array8ToStr:=S;
end;

Procedure HexaToArray8(const S: String;var BinBuf: PTArrayByte);
var
 i        :Byte;
 s1       :String[3];
begin
 for i:=0 to 7 do
 begin
  s1:='$'+S[1+(i*2)]+S[1+((i*2)+1)];
  BinBuf^[i]:=Byte(Va(s1));
 end;
end;

Procedure SplitHostIPs(const HIPs: String;var Host,IP: String);
var
 i        :Byte;

begin
 host:='';
 IP:='';
 i:=Pos('(',HIPs);
 if i=0 then
 begin
  IP:=HIPs;
 end else begin
  host:=Copy(HIPs,1,pos(' ',HIPs)-1);
  IP:=Copy(HIPs,i+1,pos(')',HIPs)-i-1);
 end;
end;

Function HostIPsToIPs(const HIPs: String): String;
var
 i        :Byte;
 s        :String;
begin
 i:=Pos('(',HIPs);
 if i=0 then
 begin
  s:=HIPs
 end else begin
  s:=Copy(HIPs,i+1,pos(')',HIPs)-i-1);
 end;
 HostIPsToIPs:=s;
end;

Function ReverseHash(var BinBuf: PTArrayByte): String;
begin
 ReverseHash:='<not implemented>';
end;

Function ReverseLookupIPs(IPs :String): String;
var
 MyIP   :Pointer;
 aIP    :LongInt;
 p      :PHostEnt;

begin
 aIP:=inet_addr(pchar(IPs));
 MyIP:=@aIP;
 p:=gethostbyaddr(myIP,4,AF_INET);
 if (p=nil) then
  ReverseLookupIPs:='<no-answer>'
 else
  ReverseLookupIPs:=String(p^.h_name);

 Application.ProcessMessages;
end;

Function SelectAPath(const spath: String): String;
begin
 With Form1.OpenDialog1 do
 begin
  DefaultExt:='';
  Filename:='';
  Filter:='All files (*.*)|*.*';
  InitialDir:=spath;
  Title:='Choose a file';
  if Execute then
    result:=GetProgramPath(Form1.OpenDialog1.FileName)
  else result:=spath;
 end; 
end;

Function SelectAFile(const aTitle: String;var spath: String): Boolean;
begin
 With Form1.OpenDialog1 do
 begin
  DefaultExt:='';
  Filename:='';
  Filter:='All files (*.*)|*.*';
  InitialDir:=spath;
  Title:=aTitle;
  if Execute then begin
    spath:=Form1.OpenDialog1.FileName;
    result:=true;
  end else result:=false;
 end;
end;

Procedure SearchBarEnabled(const bool: Boolean);
begin
 With Form1 do
 begin
  SearchEdit.Enabled:=Bool;
  SearchEdit.Refresh;
  SearchButton.Enabled:=Bool;
  SearchButton.Refresh;
 end;
end;

Procedure PopulateUserList;
var
 ListItem   :TListItem;
 i          :Integer;
begin
 With Form1.UserView do
 begin
  Items.Clear;
  Columns[0].Width:=ColumnTextWidth;
  Columns[1].Width:=ColumnTextWidth;
  for i:=0 to Users.Count-1 do
  begin
   ListItem:=Items.Add;
   ListItem.Caption:=Users.Strings[i];
   ListItem.SubItems.Add(Passwords.Strings[i]);
  end;
 end;
end;

Procedure PopulateNewsList;
var
 ListItem   :TListItem;
 i          :Integer;
begin
 With Form1.NewsList do
 begin
  Items.Clear;
  Form1.NewsList.Enabled:=False;
  for i:=0 to Newsgroups.Count-1 do
  begin
   ListItem:=Items.Add;
   ListItem.Caption:=Newsgroups.Names[i];
  end;
  Form1.NewsList.Enabled:=True;
  Columns[0].Width:=ColumnTextWidth;
 end;
end;

Procedure UpdateCatBoxes(Cats: Word);
begin
 With Form1 do
 begin
  CatBox0.Checked:=Cats and 1=1;
  CatBox1.Checked:=Cats and 2=2;
  CatBox2.Checked:=Cats and 4=4;
  CatBox3.Checked:=Cats and 8=8;
  CatBox4.Checked:=Cats and 16=16;
  CatBox5.Checked:=Cats and 32=32;
  CatBox6.Checked:=Cats and 64=64;
  CatBox7.Checked:=Cats and 128=128;
  CatBox8.Checked:=Cats and 256=256;
  CatBox9.Checked:=Cats and 512=512;
  CatBox10.Checked:=Cats and 1024=1024;
  CatBox11.Checked:=Cats and 2048=2048;
  CatBox12.Checked:=Cats and 4096=4096;
  CatBox13.Checked:=Cats and 8192=8192;
  CatBox14.Checked:=Cats and 16384=16384;
  CatBox15.Checked:=Cats and 32768=32768;
 end;
end;

Procedure UpdateCatBoxes2(Cats: Word);
begin
 With Form1 do
 begin
  Cat2Box0.Checked:=Cats and 1=1;
  Cat2Box1.Checked:=Cats and 2=2;
  Cat2Box2.Checked:=Cats and 4=4;
  Cat2Box3.Checked:=Cats and 8=8;
  Cat2Box4.Checked:=Cats and 16=16;
  Cat2Box5.Checked:=Cats and 32=32;
  Cat2Box6.Checked:=Cats and 64=64;
  Cat2Box7.Checked:=Cats and 128=128;
  Cat2Box8.Checked:=Cats and 256=256;
  Cat2Box9.Checked:=Cats and 512=512;
  Cat2Box10.Checked:=Cats and 1024=1024;
  Cat2Box11.Checked:=Cats and 2048=2048;
  Cat2Box12.Checked:=Cats and 4096=4096;
  Cat2Box13.Checked:=Cats and 8192=8192;
  Cat2Box14.Checked:=Cats and 16384=16384;
  Cat2Box15.Checked:=Cats and 32768=32768;
 end;
end;



Procedure DoGetCyberPData;
var
 DelphiIni  :TIniFile;
 S          :String;
 pwd8       :PTArrayByte;
 i          :SmallInt;

 h1,h2      :LongInt;

begin
 Form1.ProgressLabel.Caption:='Loading cyberp.ini ...';
 Form1.ProgressLabel.Refresh;
 Form1.ProgressBar.Max:=4;

 Users.Clear;
 Passwords.Clear;

 { write out the decrypted file to make it easier to handle }
 AssignFile(F,GetProgramPath(ParamStr(0))+'zzcptemp.ini');
 Rewrite(F,1);
 BlockWrite(F,INIBuf^[0],INIBufSize);
 CloseFile(F);
 FreeMem(INIBuf,INIBufSize);

 DelphiIni:=TIniFile.Create(GetProgramPath(ParamStr(0))+'zzcptemp.ini');
 GetMem(pwd8,8);

 Form1.ProgressBar.Position:=1;

 { Add administrator }
 S:=DelphiIni.ReadString('Cyber Patrol','HQ PWD','');
 if length(S)=16 then
 begin
  h1:=va('$'+copy(S,1,8));
  h2:=va('$'+copy(S,9,8));
  S:=BreakHash(h1,h2);
  Users.Add('Administrator');
  Passwords.Add(S);
 end;
 Form1.ProgressBar.Position:=2;

 { Add deputy }
 S:=DelphiIni.ReadString('Cyber Patrol','DEPUTY PWD','');
 if length(S)=16 then
 begin
  HexaToArray8(S,pwd8);
  Decrypt(pwd8,8);
  S:=Array8ToStr(pwd8);
  Users.Add('Deputy');
  Passwords.Add(S);
 end;
 Form1.ProgressBar.Position:=3;

 { Add any other users }
 i:=0;
 repeat
  inc(i);
  S:=DelphiIni.ReadString('User'+Lz(i,2),'Password','');
  if(Length(S)=16) then
  begin
   HexaToArray8(S,pwd8);
   Decrypt(pwd8,8);
   S:=Array8ToStr(pwd8);
   Passwords.Add(S);
   S:=DelphiIni.ReadString('Cyber Patrol','UserName'+Lz(i,2),'');
   Users.Add(S);
  end;
 until (S='') or (i<1);
 Form1.ProgressBar.Position:=4;

 FreeMem(pwd8,8);
 DelphiIni.Free;
 DeleteFile(GetProgramPath(ParamStr(0))+'zzcptemp.ini');
 Form1.ProgressLabel.Caption:='Loaded cyberp.ini';
 Form1.ProgressBar.Position:=0;
 if Users.Count>0 then Form1.Generatereport1.Enabled:=True;

end;

Function GetTableWithID(const ID: Word): PTNOTHeaderEntry;
var
 AHeaderEntry   :PTNOTHeaderEntry;
 i              :Integer;
begin
 result:=nil;
 for i:=0 to NOTHeader^.TblEntries-1 do
 begin
  AHeaderEntry:=Addr(NOTBuf^[SizeOf(TNOTHeader)+(i*SizeOf(TNOTHeaderEntry))]);
  if AHeaderEntry^.TblType=ID then
  begin
    result:=AHeaderEntry;
    exit;
  end;
 end;
end;

Function GetNextTableID: PTNOTHeaderEntry;
var
 AHeaderEntry   :PTNOTHeaderEntry;

begin
 if CurrNOTTbl>=NOTHeader^.TblEntries then
  AHeaderEntry:=nil
 else begin
  AHeaderEntry:=Addr(NOTBuf^[SizeOf(TNOTHeader)+(CurrNOTTbl*SizeOf(TNOTHeaderEntry))]);
  Inc(CurrNOTTbl);
 end;
 GetNextTableID:=AHeaderEntry;
end;

Procedure doProcessIPEntries(const HeaderEntry: PTNOTHeaderEntry);
var
 AByteArray     :PByteArray;
 i,j,k          :LongInt;
 ATreeNode      :TTreeNode;

begin
 { do simple IP/IP-alias table }
 if HeaderEntry<>nil then
 begin
  Form1.ProgressLabel.Caption:='Processing IP Aliases ...';
  Form1.ProgressBar.Max:=HeaderEntry^.Size-2;
  Form1.ProgressBar.Position:=0;
  Form1.ProgressLabel.Refresh;

  With Form1.IPView.Items do
  begin
   AByteArray:=Addr(NOTBuf^[HeaderEntry^.Offset]);
   i:=2; { skip 'SD' start }
   repeat
    ATreeNode:=Add(nil,
     st(AByteArray^[i+0])+'.'+st(AByteArray^[i+1])+'.'+
     st(AByteArray^[i+2])+'.'+st(AByteArray^[i+3]));
    j:=AByteArray^[i+4];
    inc(i,5);
    if j>0 then { add children }
     for k:=1 to j do
     begin
      AddChild(ATreeNode,
       st(AByteArray^[i+0])+'.'+st(AByteArray^[i+1])+'.'+
       st(AByteArray^[i+2])+'.'+st(AByteArray^[i+3]));
      Inc(i,4);
     end;
    if i mod 200=1 then Form1.ProgressBar.Position:=i;
   until i>=HeaderEntry^.Size-2;
  end;
 end;

 if Form1.IPView.Items.Count>0 then
 begin
  Form1.Importhosts1.Enabled:=True;
  Form1.Exporthosts1.Enabled:=True;
  Form1.ExportunresolvedIPs1.Enabled:=True;
  Form1.Generatereport1.Enabled:=True;  
 end;

 Form1.ProgressLabel.Caption:='';
 Form1.ProgressBar.Position:=0;
end;

Procedure doProcessNewsEntries(const HeaderEntry: PTNOTHeaderEntry);
var
 AByteArray     :PByteArray;
 i              :LongInt;
 ng             :String;
 cat            :Integer;

begin
 if HeaderEntry<>nil then
 begin
  Form1.ProgressLabel.Caption:='Processing newsgroup filters ...';
  Form1.ProgressBar.Max:=HeaderEntry^.Size-2;
  Form1.ProgressBar.Position:=0;
  Form1.ProgressLabel.Refresh;

  AByteArray:=Addr(NOTBuf^[HeaderEntry^.Offset]);
  i:=2; { skip 'SD' start }
  repeat
   SetLength(ng,AByteArray^[i]-3);
   cat:=Word(AByteArray^[i+2] shl 8)+AByteArray^[i+1];
   Move(AByteArray^[i+3],ng[1],AByteArray^[i]-3);
   Newsgroups.Add(ng+'='+st(cat));
   Inc(I,AByteArray^[i]);
   if i mod 10=0 then Form1.ProgressBar.Position:=i;
  until i>HeaderEntry^.Size-3; { should check addr. instead }
 end;

 if Newsgroups.Count>0 then Form1.Generatereport1.Enabled:=True;

 Form1.ProgressLabel.Caption:='';
 Form1.ProgressBar.Position:=0;
end;

Procedure doProcessURLEntries(const HeaderEntry: PTNOTHeaderEntry);
var
 AByteArray     :PByteArray;
 i              :LongInt;
 b              :Byte;
 ANode          :POURLEntry;
 ATreeNode      :TTreeNode;

 NetNodes       :Array[0..255] of TTreeNode;

 theIP          :Array[0..3] of Byte;
 theIPs         :String[16];
 theHash        :LongInt;
 theCat         :Word;
 theSubLen      :Byte;

begin
 StatisticsTotalURLHashes:=0; { ** shouldn't be here if allowing append }

 if HeaderEntry<>nil then
 begin

  Form1.ProgressLabel.Caption:='Processing URL entries ...';
  Form1.ProgressBar.Max:=HeaderEntry^.Size-2;
  Form1.ProgressBar.Position:=0;
  Form1.ProgressLabel.Refresh;

  for b:=0 to 255 do NetNodes[b]:=nil;

  AByteArray:=Addr(NOTBuf^[HeaderEntry^.Offset]);
  Form1.ProgressBar.Max:=HeaderEntry^.Size-3;
  Form1.URLView.Visible:=False;
  i:=2; { skip 'SD' start }
  repeat
   New(Anode,Create);
   Move(AByteArray^[i],theIP[0],4);
   theIPs:=st(theIP[0])+'.'+st(theIP[1])+'.'+st(theIP[2])+'.'+st(theIP[3]);
   theCat:=Word(AByteArray^[i+5] shl 8)+AByteArray^[i+4];
   ANode.IP:=theIPs;
   ANode.FiltCat:=theCat;

   { find correct cluster }
   if NetNodes[theIP[0]]=nil then
   begin
    NetNodes[theIP[0]]:=Form1.URLView.Items.Add(nil,st(theIP[0])+'.*')
   end;
   ATreeNode:=Form1.URLView.Items.AddChildObject(NetNodes[theIP[0]],theIPs,ANode);
   Inc(i,6);
   if theCat=0 then { parse sub-header }
   begin
    repeat
     New(Anode,Create);
     ANode.IP:=theIPs;
     theSubLen:=AByteArray^[i];
     theCat:=Word(AByteArray^[i+2] shl 8)+AByteArray^[i+1];
     Inc(i,3);
     for b:=1 to ((theSubLen-3) div 4) do { add hashes }
     begin
      Move(AByteArray^[i],theHash,4);
      ANode.AddHash(theHash);
      Inc(StatisticsTotalURLHashes);
      Inc(i,4);
     end;
     ANode.FiltCat:=theCat;
     Form1.URLView.Items.AddChildObject(ATreeNode,ANode.GetURL,ANode);
    until AByteArray^[i]=0;
    Inc(i); { skip terminator }
   end;
   if i mod 200=1 then Form1.ProgressBar.Position:=i;
  until i>(HeaderEntry^.Size-3); { div 20 **MAGICSPEED** should check addr instead, safer }
  Form1.URLView.Visible:=True;
 end;

 if Form1.URLView.Items.Count>0 then
 begin
  Form1.Importhosts1.Enabled:=True;
  Form1.Exporthosts1.Enabled:=True;
  Form1.ExportunresolvedIPs1.Enabled:=True;
  Form1.Generatereport1.Enabled:=True;
  Form1.Exportdictionary1.Enabled:=True;
  Form1.ExportURLhashes1.Enabled:=True;
 end;

 Form1.ProgressLabel.Caption:='';
 Form1.ProgressBar.Position:=0;
end;

Procedure DoGetCyberNotData;
begin
 Form1.Refresh;

 CurrNOTTbl:=0; { reset counter }
 NOTHeaderEntry:=GetNextTableID;
 while NOTHeaderEntry<>nil do
 begin
  case NOTHeaderEntry^.TblType of
   $41:     doProcessIPEntries(NOTHeaderEntry);
   $47,$4E: doProcessNewsEntries(NOTHeaderEntry);
   $49,$4F: doProcessURLEntries(NOTHeaderEntry);
  end;
  NOTHeaderEntry:=GetNextTableID;
 end;

 Form1.ProgressBar.Position:=0;
 Form1.ProgressLabel.Caption:='.not file processed.';

end;

Function GetURLNodeType(const node: TTreeNode): Byte;
var
 typ   :Byte;
begin
 if node.level=0 then typ:=NODE_IS_NET else
  if node.level=1 then typ:=NODE_IS_IP else
   if node.level=2 then typ:=NODE_IS_URL else typ:=NODE_IS_UNKNOWN;
 GetURLNodeType:=typ;  
end;

procedure TForm1.Exit1Click(Sender: TObject);
begin
 WSACleanup;
 Halt;
end;

procedure TForm1.Open1Click(Sender: TObject);
begin

 With OpenDialog1 do
 begin
  DefaultExt:='.ini';
  Filename:='cyberp.ini';
  Filter:='INI files (*.ini)|*.ini|All files (*.*)|*.*';
  InitialDir:=CfgDefaultPathEdit.Text;
  Title:='Choose the cyberp.ini file to open';
  if Execute then begin
    AssignFile(F,Filename);
    Reset(F,1);
    INIBufSize:=FileSize(F);
    GetMem(INIBuf,INIBufSize);
    BlockRead(F,INIBuf^[0],INIBufSize);
    CloseFile(F);
    Decrypt(INIBuf,INIBufSize);
    DoGetCyberPData;
    PopulateUserList;
  end;
 end;
end;

Function doAskLoadDecryptNOTFile: Boolean;
{ Loads NOT file into buffer, freeing it first if already allocated. }
begin

 With Form1.OpenDialog1 do
 begin
  DefaultExt:='.not';
  Filename:='cyber.not';
  Filter:='NOT files (*.not)|*.not|All files (*.*)|*.*';
  InitialDir:=Form1.CfgDefaultPathEdit.Text;
  Title:='Choose the .NOT/.HOT file to open';
  result:=FALSE;
  if Execute then begin
    AssignFile(F,Filename);
    Reset(F,1);

    { release old buffer }
    if (NOTBuf<>nil) then Freemem(NOTBuf,NOTBufSize);
    NOTBufSize:=FileSize(F);
    GetMem(NOTBuf,NOTBufSize);
    BlockRead(F,NOTBuf^[0],NOTBufSize);
    CloseFile(F);
    if NOT ( (NOTBuf^[4]=Ord('C')) or (NOTBuf^[4]=Ord('H'))) and (NOTBuf^[5]=Ord('H')) then Decrypt(NOTBuf,NOTBufSize);
    Result:=TRUE;

    NOTHeader:=Pointer(NOTBuf);
    CurrNOTTbl:=0; { reset counter }
  end;
 end;

end;

procedure TForm1.OpenNOTlist1Click(Sender: TObject);
begin
 if doAskLoadDecryptNOTFile then
 begin
  DoGetCyberNotData;
  PopulateNewsList;
  Form1.OpenNOTlist1.Enabled:=false; { ** safety as long as no memory/list management }
 end;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
 NewColumn          :TListColumn;
 wVersionRequested  :Word;
 wsaData            :TWSAData;

begin
 Form1.Caption:='Cyber Patrol Hack v'+VERSIONSTRING+' - (C)2000 Eddy L O Jansson';

 InfoHeader.Caption:='Cyber Patrol Hack v'+VERSIONSTRING;

 NOTBuf:=nil;
 NOTBufSize:=0;

 {Start up WinSock}
 wVersionRequested:=MAKEWORD(1,1);
 WSAStartup(wVersionRequested,wsaData);

 Users:=TStringList.Create;
 Passwords:=TStringList.Create;
 Newsgroups:=TStringList.Create;

 with UserView do
 begin
  ViewStyle := vsReport;
  NewColumn:=Columns.Add;
  NewColumn.Width:=100;
  NewColumn.Caption:='Name';

  NewColumn:=Columns.Add;
  NewColumn.Width:=80;
  NewColumn.Caption:='Password';
 end;

 with NewsList do
 begin
  ViewStyle := vsReport;
  NewColumn:=Columns.Add;
  NewColumn.Width:=190;
  NewColumn.Caption:='Newsgroup mask';
 end;

 CfgINIFilename:=GetProgramPath(ParamStr(0))+'cphack.ini';
 LoadConfiguration(CfgINIFilename);

 { show first page }
 Form1.PageControl1.activepage:=Form1.TabSheet1;
end;

procedure TForm1.NewsListSelectItem(Sender: TObject; Item: TListItem;Selected: Boolean);
var
 w    :Word;
begin
 w:=Va(Newsgroups.Values[Newsgroups.Names[Item.Index]]);
 Form1.GroupCat.Caption:='Filter Categories: 0x'+IntToHex(w,4);
 UpdateCatBoxes(w);
end;

procedure OpenHostIPs(const HIPs: String);
var
 S        :String;
begin
 S:='http://'+HostIPsToIPs(HIPs)+'/'; { return only IP-part }
 ShellExecute(Application.handle, nil, Pchar(s), nil, nil, SW_SHOWNORMAL);
end;

procedure TForm1.IPViewMouseDown(Sender: TObject; Button: TMouseButton;Shift: TShiftState; X, Y: Integer);
var
 AHitTest :THitTests;
begin
 if Button=mbright then
 begin
  AHitTest:=IPView.GetHitTestInfoAt(X,Y);
  if (htOnItem in AHitTest) {or (htOnLabel in AHitTest)} then
  begin
   IPAliasPopup.Items[0].Enabled:=True;
   IPAliasPopup.Items[1].Enabled:=True;
  end else begin
   IPAliasPopup.Items[0].Enabled:=False;
   IPAliasPopup.Items[1].Enabled:=False;
  end;
  IPAliasPopup.Popup(IPView.ClientOrigin.x+x,IPView.ClientOrigin.y+y);
 end;

end;

Function CheckIfShouldLookup(const S1: String): Boolean;
begin
 if Form1.CfgReCheckNoAnswer.Checked then
   result:=true
 else
   result:=Pos('(',S1)=0;
end;

Function doLookupIP(ATreeNode: TTreeNode): Boolean;
var
 S1,S2,S3         :String;

begin
 result:=False;
 S1:=ATreeNode.Text;
 if CheckIfShouldLookup(S1) then
 begin
  SplitHostIPs(S1,S2,S3);
  Form1.ProgressLabel.Caption:='Resolving '+S3+ ' ...';
  Form1.ProgressLabel.Refresh;
  S2:=ReverseLookupIPs(S3);
  if S2>'' then
  begin
   ATreeNode.Text:=S2+' ('+S3+')';
   Form1.ProgressLabel.Caption:='Resolved '+S2+ '.';
   result:=true;
  end;
 end;
 Application.ProcessMessages;
end;

procedure TForm1.Lookup1Click(Sender: TObject);
begin
 doLookupIP(IPView.Selected);
end;

Procedure doLookupIPAliases(const typ: Byte);
var
 cnt              :LongInt;
 ATreeNode        :TTreeNode;

begin
 ATreeNode:=Form1.IPView.Items.GetFirstNode;
 Cnt:=0;
 While ATreeNode<>nil do
 begin
   if (doLookupIP(ATreeNode)) then
   begin
    Inc(cnt);
    Form1.URLStatLbl1.Caption:='Number of IPs looked up: '+st(cnt);;
    Form1.URLStatBar.Position:=cnt;
    Form1.URLStatBox.Refresh;
   end;

   if Form1.TheCancelButton.Tag=-1 then
   begin
    ATreeNode.Selected;
    Form1.ProgressLabel.Caption:='Hostname lookup aborted.';
    break;
   end;

   if typ=1 then
    ATreeNode:=ATreeNode.getNextSibling { root nodes }
   else
    ATreeNode:=ATreeNode.GetNext; { all nodes }
 end;
end;

Procedure doLookupMux(const typ: Byte);
begin
 With Form1 do begin
  URLStatLbl1.Caption:='Number of IPs looked up: 0';
  URLStatLbl1.Refresh;
  TheCancelButton.Visible:=True;
  TheCancelButton.Tag:=0;
  TheCancelButton.Refresh;
 end;

 case typ of
  1: doLookupIPAliases(1); { only root nodes }
  2: doLookupIPAliases(2); { all nodes }
 end;

 With Form1 do begin
  TheCancelButton.Visible:=False;
  TheCancelButton.Tag:=0;
  TheCancelButton.Refresh;
 end; 

end;

procedure TForm1.Lookuprootnodes1Click(Sender: TObject);
begin
 doLookupMux(1);
end;

procedure TForm1.LookupAll1Click(Sender: TObject);
begin
 doLookupMux(2);
end;

Procedure ApplyHostsToAliasTree(const FirstNode: TTreeNode;var HostTable: POHashStr);
var
 ATreeNode       :TTreeNode;
 cnt             :Integer;
 s               :String;
 s2              :PString;

begin
 Form1.ProgressLabel.Caption:='Importing hostnames into IP Alias tree ...';
 Form1.ProgressLabel.Refresh;
 Form1.ProgressBar.Max:=Form1.IPView.Items.Count;

 ATreeNode:=FirstNode;
 cnt:=0;
 While ATreeNode<>nil do
 begin
  S:=HostIPsToIPs(ATreeNode.Text);
  s2:=HostTable^.Get(s);
  if s2<>nil then
   begin
    ATreeNode.Text:=S2^+' ('+S+')';
    Form1.URLStatBar.Position:=Form1.URLStatBar.Position+1;
   end;
  ATreeNode:=ATreeNode.GetNext;
  Inc(cnt);
  if cnt mod 200=1 then Form1.ProgressBar.Position:=cnt;
 end;
 Form1.ProgressBar.Position:=0;
 Form1.URLStatLbl1.Caption:='Hostnames imported: '+St(Form1.URLStatBar.Position);
end;

Procedure ApplyHostsToURLTree(const FirstNode: TTreeNode;var HostTable: POHashStr);
var
 ATreeNode       :TTreeNode;
 cnt             :Integer;
 s               :String;
 s2              :PString;

begin
 cnt:=0;
 Form1.ProgressBar.Max:=Form1.URLView.Items.Count;
 Form1.ProgressLabel.Caption:='Importing hostnames into URL tree ...';
 Form1.ProgressLabel.Refresh;

 ATreeNode:=FirstNode;
 While ATreeNode<>nil do
 begin
  if GetURLNodeType(ATreeNode)=NODE_IS_IP then
  begin
   S:=HostIPsToIPs(ATreeNode.Text);
   s2:=HostTable^.Get(s);
   if s2<>nil then
   begin
    ATreeNode.Text:=S2^+' ('+S+')';
    Form1.URLStatBar.Position:=Form1.URLStatBar.Position+1;
   end;
  end;
  Inc(cnt);
  if cnt mod 200=1 then Form1.ProgressBar.Position:=cnt;
  ATreeNode:=ATreeNode.GetNext;
 end;
 Form1.ProgressBar.Position:=0;
 Form1.URLStatLbl1.Caption:='Hostnames imported: '+St(Form1.URLStatBar.Position);
end;

Procedure TForm1.Importhosts1Click(Sender: TObject);
var
 F               :File;
 T               :TextFile;
 hosts           :POHashStr;
 S,n,v           :String;
 currsize        :LongInt;

begin
 With OpenDialog1 do
 begin
  DefaultExt:='';
  Filename:='hosts';
  Filter:='All files (*.*)|*.*';
  InitialDir:=CfgDefaultPathEdit.Text;
  Title:='Choose the hosts file to open';
  if Execute then begin

    AssignFile(F,Filename);
    Reset(F,1);
    ProgressBar.Max:=FileSize(F);
    CloseFile(F);
    ProgressBar.Position:=0;
    ProgressLabel.Caption:='Reading hosts into hash-table ...';
    ProgressLabel.Refresh;

    AssignFile(T,Filename);
    Reset(T);

    New(hosts,create(1009));

    CurrSize:=0;
   { Read hosts file into collection }
    While NOT EoF(T) do
    begin
     readln(T,s);
     Inc(CurrSize,Length(S)+2);
     s:=trim(s);
     if (length(s)>0) and (s[1] in ['0'..'9']) then
     begin { fragile thing, ought to be fixed. }
      s:=s+' ';
      v:=Copy(s,1,pos(' ',s)-1);
      delete(s,1,pos(' ',s));
      s:=trim(s)+' ';
      n:=Copy(s,1,pos(' ',s)-1);
      if CurrSize mod 8=0 then ProgressBar.Position:=CurrSize;
      if (v<>'') and (n<>'') then
      begin
       if (NOT CFGSkipNoAnswerOnImport.Checked) OR
          (CFGSkipNoAnswerOnImport.Checked and (n<>'<no-answer>')) then hosts^.Add(v,n);
      end;
     end;
    end;
    closefile(T);

    { Assign values from collection into trees }
    URLStatLbl1.Caption:='Hostnames mapped ...';
    URLStatLbl1.Refresh;
    URLStatBar.Max:=IPView.Items.Count+URLView.Items.Count;

    ApplyHostsToAliasTree(IPView.Items.GetFirstNode,hosts);
    ApplyHostsToURLTree(URLView.Items.GetFirstNode,hosts);
    Dispose(hosts,Destroy);
    ProgressLabel.Caption:='Hostnames imported.';
  end; { execute ..}
 end; { with opendialog }

end;

procedure TForm1.Exporthosts1Click(Sender: TObject);
var
 T               :TextFile;
 ATreeNode       :TTreeNode;
 n,v             :String;
 hits,
 cnt             :LongInt;


begin
 With SaveDialog1 do
 begin
  DefaultExt:='';
  Filename:='hosts';
  Filter:='All files (*.*)|*.*';
  InitialDir:=CfgDefaultPathEdit.Text;
  Title:='Choose the hosts file to save to';
  if Execute then begin
    ProgressLabel.Caption:='Exporting hosts ...';
    ProgressBar.Max:=IPView.Items.Count+URLView.Items.Count;
    ProgressBar.Position:=0;
    ProgressLabel.Refresh;

    AssignFile(T,Filename);
    Rewrite(T);
    WriteLn(T,'# exported by CPHack');
    cnt:=0;
    hits:=0;
     ATreeNode:=IPView.Items.GetFirstNode;
     While ATreeNode<>nil do
     begin
      SplitHostIPs(ATreeNode.Text,v,n);
      if v>'' then
      begin
       WriteLn(T,n,' ',v);
       inc(hits);
      end;
      ATreeNode:=ATreeNode.GetNext;
      inc(cnt);
      if cnt mod 200=1 then ProgressBar.Position:=cnt;
     end;

     ATreeNode:=URLView.Items.GetFirstNode;
     While ATreeNode<>nil do
     begin
      SplitHostIPs(ATreeNode.Text,v,n);
      if v>'' then
      begin
       WriteLn(T,n,' ',v);
       inc(hits);
      end;
      ATreeNode:=ATreeNode.GetNext;
      inc(cnt);
      if cnt mod 200=1 then ProgressBar.Position:=cnt;
     end;
    CloseFile(T);

    ProgressLabel.Caption:=St(hits)+' hosts exported.';
    ProgressBar.Position:=0;

  end; { execute ..}
 end; { with savedialog }
end;

Function HostIPsToLI(const s: String): String;
var
 outs,s1,s2    :String;
begin
 outs:='';
 SplitHostIPs(s,s1,s2);
 if s1='<no-answer>' then
 begin
  outs:='<a href="http://'+s2+'/">'+s2+'</a> &lt;no-answer&gt;';
 end else begin
  if s1<>'' then outs:=s1+' ('; {'<a href="http://'+s1+'/">'+s1+'</a> (';}
  outs:=outs+'<a href="http://'+s2+'/">'+s2+'</a>';
  if s1<>'' then outs:=outs+')';
 end;
 HostIPsToLI:='<li>'+outs+'</li>';
end;

procedure TForm1.Generatereport1Click(Sender: TObject);
const
 maskchars     :String[16]='0123456789ABCDEF';

var
 ATreeNode,
 ATreeNode2    :TTreeNode;
 cnt           :Array[0..15] of Integer;
 T             :TextFile;
 s,s2          :String;
 i             :LongInt;
 w             :Word;
 nodetype,
 b             :Byte;

 prevwasoutput :Boolean;

 Function GetMaskTableString(const mask: Word): String;
 var
  s2                :String;
  b                 :Byte;
  x                 :Word;
 begin
  x:=mask;
  s2:='';
  for b:=0 to 15 do
  begin
   s2:=s2+'<td>';
   if (w and 1)=1 then s2:=s2+maskchars[b+1];
   s2:=s2+'</td>';
   w:=w shr 1;
  end;
  result:='<tr>'+s2+'<td>'+IntToHex(x,4)+'</td>';
 end;


begin

 With SaveDialog1 do
 begin
  DefaultExt:='.html';
  Filename:='cphack-report';
  Filter:='HTML documents (*.html)|*.html|All files (*.*)|*.*';
  InitialDir:=CfgDefaultPathEdit.Text;
  Title:='Choose a filename to export the report as';
  if Execute then begin

 Form1.Refresh;

 AssignFile(T,filename);
 ReWrite(T);

 WriteLn(T,'<html>');
 WriteLn(T,' <head><title>Cyber Patrol Report</title></head>');
 WriteLn(T,'<body>');

 WriteLn(T,'<h1>Cyber Patrol Report</h1>');
 WriteLn(T,'<p>generated by <a href="http://hem.passagen.se/eddy1/reveng/">CPHack</a> v'+VERSIONSTRING+'</p>');

 if UserView.Items.Count>0 then
 begin
  WriteLn(T,'<h2>Users Report</h2>');
  WriteLn(T,'<table>');
  WriteLn(T,'<tr><th>Name</th><th>Password</th></tr>');
  for i:=1 to UserView.Items.Count-1 do
  begin
   WriteLn(T,'<tr><td>',UserView.Items.Item[i].Caption,'</td><td>',Trim(UserView.Items.Item[i].SubItems[0]),'</td></tr>');
  end;
  WriteLn(T,'</table>');
 end; { users }

 if (NewsList.Items.Count>0) then
 begin
  Form1.ProgressLabel.Caption:='Generating report: Newsgroups';
  Form1.ProgressLabel.Refresh;
  Form1.ProgressBar.Max:=NewsList.Items.Count;

  WriteLn(T,'<h2>Newsgroups Report</h2>');

  { count news entries }
  for b:=0 to 15 do cnt[b]:=0;
  for i:=1 to NewsList.Items.Count-1 do
  begin
   w:=Va(Newsgroups.Values[Newsgroups.Names[i-1]]);
   for b:=0 to 15 do begin
    if (w and 1)=1 then inc(cnt[b]);
    w:=w shr 1;
   end;
  end;

  { output newsgroups categories and counts table }
  S:='0123456789ABCDEF';
  WriteLn(T,'<table>');
  WriteLn(T,'<tr><th>Bit</th><th>Category</th><th>Entries flagged</th></tr>');
  for b:=0 to 15 do begin
   Write(T,' <tr>');
   Write(T,'<td>',S[b+1],'</td>');
   Write(T,'<td>',Categories[b],'</td>');
   Write(T,'<td>',Cnt[b],'</td>');
   WriteLn(T,'</tr>');
  end;
  WriteLn(T,'<tr><td></td><td><strong>Total newsgroup masks</strong></td><td><strong>',NewsList.Items.Count,'</strong></td></tr>');
  WriteLn(T,'</table>');

  If (not Form1.CfgExcludeNewsgroups.Checked) then
  begin
   WriteLn(T,'<h3>Detailed newsgroup masking report</h3>');

   { output newsgroups list }
   WriteLn(T,'<table>');
   for i:=1 to NewsList.Items.Count-1 do
   begin
    s2:=GetMaskTableString(va(Newsgroups.Values[Newsgroups.Names[i-1]]));
    WriteLn(T,s2+'<td>',Newslist.Items.Item[i-1].Caption,'</td></tr>');
    Form1.ProgressBar.Position:=i;
   end;
   WriteLn(T,'</table>');
  end; { // detailed newsgroups }
  Form1.ProgressLabel.Caption:='';
  Form1.ProgressBar.Position:=0;

 end; { newsgroups }

 { IP Aliases }
 if (Form1.IPView.Items.Count>0) and (not Form1.CfgExcludeIPAliases.Checked) then
 begin
  Form1.ProgressLabel.Caption:='Generating report: IP Aliases';
  Form1.ProgressLabel.Refresh;
  Form1.ProgressBar.Max:=Form1.IPView.Items.Count;

  WriteLn(T,'<h2>IP Aliases Report</h2>');

  WriteLn(T,'<ul>');
  ATreeNode:=Form1.IPView.Items.GetFirstNode;
  i:=0;
  While ATreeNode<>nil do
  begin
   { write root node }
   WriteLn(T,HostIPsToLI(ATreeNode.Text));

   if ATreeNode.HasChildren then  { recursion sucks }
   begin
    ATreeNode2:=ATreeNode.GetNext;
    Inc(i);
    WriteLn(T,'<ul>');
    repeat { write any children }
     WriteLn(T,HostIPsToLI(ATreeNode2.Text));
     ATreeNode2:=ATreeNode2.GetNextSibling;
     Inc(i);
    until ATreeNode2=nil;
    WriteLn(T,'</ul>');
   end;
    ATreeNode:=ATreeNode.GetNextSibling; { all nodes }
    Inc(i);
    if i mod 200=1 then Form1.ProgressBar.Position:=i;
  end;
  WriteLn(T,'</ul>');
  Form1.ProgressLabel.Caption:='';
  Form1.ProgressBar.Position:=0;
 end; { IP Aliases }


 { URL database report }
 if (URLView.Items.Count>0) then
 begin
  Form1.ProgressLabel.Caption:='Generating report: URL Database';
  Form1.ProgressLabel.Refresh;
  Form1.ProgressBar.Max:=URLView.Items.Count;

  WriteLn(T,'<h2>URL Database Report</h2>');

  { reset categories count }
  for b:=0 to 15 do cnt[b]:=0;

  ATreeNode:=URLView.Items.GetFirstNode;
  i:=0;
  While ATreeNode<>nil do
  begin
   if ATreeNode.Level>0 then
   begin
    w:=POURLEntry(ATreeNode.Data)^.FiltCat;
    if w>0 then
    begin
     Inc(i);
     for b:=0 to 15 do begin
      if (w and 1)=1 then inc(cnt[b]);
      w:=w shr 1;
     end;
    end;
   end;
   ATreeNode:=ATreeNode.GetNext;
  end;

  { output categories and counts table }
  S:='0123456789ABCDEF';
  WriteLn(T,'<table>');
  WriteLn(T,'<tr><th>Bit</th><th>Category</th><th>Entries flagged</th></tr>');
  for b:=0 to 15 do begin
   Write(T,' <tr>');
   Write(T,'<td>',S[b+1],'</td>');
   Write(T,'<td>',Categories[b],'</td>');
   Write(T,'<td>',Cnt[b],'</td>');
   WriteLn(T,'</tr>');
  end;
  WriteLn(T,'<tr><td></td><td><strong>Total URL masks</strong></td><td><strong>',i,'</strong></td></tr>');
  WriteLn(T,'</table>');

   If (not Form1.CfgExcludeURLs.Checked) then
   begin
    WriteLn(T,'<h3>Detailed URL report</h3>');

    prevwasoutput:=true;
    WriteLn(T,'<table>');
    ATreeNode:=URLView.Items.GetFirstNode;
    i:=0;
    While ATreeNode<>nil do
    begin
     Inc(i);
     nodetype:=GetURLNodeType(ATreeNode);
     { don't do root nodes }
     case nodetype of
      NODE_IS_IP:
      begin
       if (NOT Form1.CFGExclURLNotLookup.Checked) OR ((Form1.CFGExclURLNotLookup.Checked) and (Pos('(',ATreeNode.Text)>0)) then
       begin
        prevwasoutput:=true;
        if NOT ATreeNode.HasChildren then { only output if cat relevant }
        begin
         w:=POURLEntry(ATreeNode.Data)^.FiltCat;
         Write(T,GetMaskTableString(w));
        end else begin { if category irrelevant }
         Write(T,'<tr><td colspan="17"></td>');
        end;
        WriteLn(T,'<td>',ATreeNode.Text,'</td></tr>');
       end else prevwasoutput:=false;
      end;

      NODE_IS_URL:
      begin
       if prevwasoutput then
       begin
        if (NOT Form1.CFGExclURLHashes.Checked) or ((Form1.CFGExclURLHashes.Checked) and (POURLEntry(ATreeNode.data)^.isFullURL)) then
        begin
         w:=POURLEntry(ATreeNode.Data)^.FiltCat;
         Write(T,GetMaskTableString(w));
         S2:=ATreeNode.Text;
         for b:=1 to Length(S2) do
          if S2[b]='<' then S2[b]:='[' else if S2[b]='>' then S2[b]:=']';
          WriteLn(T,'<td>',S2,'</td></tr>');
        end;
       end; { skip because no corresponding IP }
      end;

     end; { case }

     if i mod 100=1 then Form1.ProgressBar.Position:=i;
     ATreeNode:=ATreeNode.GetNext;
    end;
     WriteLn(T,'</table>');


   end; { // Detailed URL database }

  end; { // URL report }

  WriteLn(T,'</body></html>');
  CloseFile(T);
  Form1.ProgressLabel.Caption:='Report written.';
  Form1.ProgressBar.Position:=0; 
  end; { did execute report }
 end;

end;

procedure TForm1.CfgSaveButtonClick(Sender: TObject);
begin
 SaveConfiguration(CfgINIFilename);
end;

procedure TForm1.CfgLoadButtonClick(Sender: TObject);
begin
 LoadConfiguration(CfgINIFilename);
end;

procedure TForm1.CfgDefaultPathButtonClick(Sender: TObject);
begin
 CfgDefaultPathEdit.Text:=SelectAPath(CfgDefaultPathEdit.Text);
end;

procedure TForm1.CfgLexiconButtonClick(Sender: TObject);
var
 S        :String;
begin
 S:=CfgLexiconEdit.Text;
 if SelectAFile('Select a dictionary',S) then CfgLexiconEdit.Text:=S;
end;

procedure TForm1.CfgPrefixButtonClick(Sender: TObject);
var
 S        :String;
begin
 S:=CfgPrefixEdit.Text;
 if SelectAFile('Select a file to supply prefixes',S) then CfgPrefixEdit.Text:=S;
end;

procedure TForm1.CfgSuffixButtonClick(Sender: TObject);
var
 S        :String;
begin
 S:=CfgSuffixEdit.Text;
 if SelectAFile('Select a file to supply suffixes',S) then CfgSuffixEdit.Text:=S;
end;

procedure TForm1.GoTo1Click(Sender: TObject);
begin
 OpenHostIPs(IPView.Selected.Text);
end;

procedure TForm1.URLOpenClick(Sender: TObject);
var
 s                 :String;
begin
 s:='http://'+POURLEntry(URLView.Selected.data)^.IP+POURLEntry(URLView.Selected.Data)^.GetURL;
 ShellExecute(Application.handle, nil, Pchar(s), nil, nil, SW_SHOWNORMAL);
end;

procedure TForm1.URLViewMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
 AHitTest :THitTests;
 typ      :Byte;
 CurrNode :TTreeNode;
begin
 if Button=mbright then
 begin
  AHitTest:=URLView.GetHitTestInfoAt(X,Y);
  if (htOnItem in AHitTest) {or (htOnLabel in AHitTest)} then
  begin
   CurrNode:=URLView.Selected;
   typ:=GetURLNodeType(CurrNode);
   case typ of
    NODE_IS_NET:
     begin
      URLOpen.Visible:=False;
      URLLookupIP.Visible:=False;
      URLLookupBranch.Visible:=True;
      URLLookupforward1.Visible:=True;
      URLLexiconURL.Visible:=False;
      URLLexiconBranch.Visible:=CurrNode.HasChildren;
      URLLexiconWholeTree.Visible:=True;
      URLGotonextfullURL1.Visible:=True;
     end;

    NODE_IS_IP:
     begin
      URLOpen.Caption:='&Open http://'+HostIPsToIPs(CurrNode.Text);
      URLOpen.Visible:=True;
      URLLookupIP.Visible:=True;
      URLLookupBranch.Visible:=False;
      URLLookupforward1.Visible:=False;
      URLLexiconURL.Visible:=False;
      URLLexiconBranch.Visible:=CurrNode.HasChildren;
      URLLexiconWholeTree.Visible:=False;
      URLGotonextfullURL1.Visible:=True;
     end;

    NODE_IS_URL:
     begin
      if Pos('<',CurrNode.Text)=0 then
      begin
       URLOpen.Caption:='&Open http://'+POURLEntry(CurrNode.Data)^.IP+POURLEntry(CurrNode.Data)^.GetURL;
       URLOpen.Visible:=True;
      end else URLOpen.Visible:=False;
       URLLookupIP.Visible:=False;
       URLLookupBranch.Visible:=False;
       URLLookupforward1.Visible:=False;
       URLLexiconURL.Visible:=True;
       URLLexiconBranch.Visible:=False;
       URLLexiconWholeTree.Visible:=False;
       URLGotonextfullURL1.Visible:=True;
     end;
   end;
   if typ<>NODE_IS_UNKNOWN then URLPopup.Popup(URLView.ClientOrigin.x+x,URLView.ClientOrigin.y+y);
  end;
 end;
end;

Procedure doLexiconOnURL(const ATreeNode: TTreeNode);
var
 lex       :POLexicon;
 s         :String;
 hits      :Integer;
begin
 { short circuit if it cannot change. }
 if (Form1.CFGLockURLs.Enabled) and (POURLEntry(ATreeNode.Data)^.isFullURL) then Exit;
 New(lex,Create);
 lex.SetFileLexicon(Form1.CfgLexiconEdit.Text);
 if Form1.CfgPrefixEnabled.Checked then
  lex.PrefixActive(lex.SetFilePrefix(Form1.CfgPrefixEdit.Text));
 if Form1.CfgSuffixEnabled.Checked then
  lex.SuffixActive(lex.SetFileSuffix(Form1.CfgSuffixEdit.Text));
 lex.Reset;
 Form1.ProgressBar.Max:=lex.lexmax;
 Form1.ProgressLabel.Caption:='Dictionary attack ...';
 Form1.ProgressLabel.Refresh;
{ ATreeNode.Selected:=True;}

 While lex.GetNextWord(s) do
 begin
  hits:=POURLEntry(ATreeNode.Data)^.AttachHashStr(S,Form1.CFGLockURLs.Enabled);
  if hits>0 then
  begin
   ATreeNode.text:=POURLEntry(ATreeNode.Data)^.GetURL;
   Form1.URLStatBar.Position:=Form1.URLStatBar.Position+hits;
   Form1.URLStatLbl1.Caption:='Number of hashes matched: '+St(Form1.URLStatBar.Position);
   if POURLEntry(ATreeNode.Data)^.isFullURL then Break;
  end;
  if lex.lexposition mod 500=1 then
  begin
   Form1.ProgressBar.Position:=lex.lexposition;
   Application.ProcessMessages;
  end;
 end;
 Form1.ProgressBar.Position:=0;
 Form1.ProgressLabel.Caption:='';
 Dispose(lex,Destroy);
end;

Procedure doLexiconOnIP(const ATreeNode: TTreeNode);
var
 MyTreeNode       :TTreeNode;

begin
 MyTreeNode:=ATreeNode.getFirstChild;
 if MyTreeNode<>nil then begin
  repeat
   doLexiconOnURL(MyTreeNode);
   MyTreeNode:=ATreeNode.GetNextChild(MyTreeNode);
  until MyTreeNode=nil;
 end;
end;

Procedure doLexiconOnNet(const ATreeNode: TTreeNode);
var
 MyTreeNode       :TTreeNode;

begin
 SearchBarEnabled(FALSE);
 MyTreeNode:=ATreeNode.getFirstChild;
 if MyTreeNode<>nil then begin
  repeat
   if MyTreeNode.HasChildren then
   begin
    MyTreeNode.Selected:=True; 
    doLexiconOnIP(MyTreeNode);
   end;
   Application.ProcessMessages;
   if Form1.TheCancelButton.Tag=-1 then
   begin
    MyTreeNode.Selected;
    Form1.ProgressLabel.Caption:='Dictionary attack aborted.';
    break;
   end;
   MyTreeNode:=ATreeNode.GetNextChild(MyTreeNode);
  until MyTreeNode=nil;
 end;
 SearchBarEnabled(TRUE);
end;

Procedure doLookupOnNet(const ATreeNode: TTreeNode;var cnt: LongInt);
var
 MyTreeNode       :TTreeNode;

begin
 MyTreeNode:=ATreeNode.getFirstChild;
 if MyTreeNode<>nil then begin
  repeat
   MyTreeNode.Selected:=True;
   if GetURLNodeType(MyTreeNode)=NODE_IS_IP then
     if(doLookupIP(MyTreeNode)) then
     begin
      Inc(cnt);
      Form1.URLStatLbl1.Caption:='Number of IPs looked up: '+st(cnt);
      Form1.URLStatLbl1.Refresh;
      Form1.URLStatBar.Position:=cnt;
     end;

   if Form1.TheCancelButton.Tag=-1 then
   begin
    MyTreeNode.Selected;
    Form1.ProgressLabel.Caption:='Hostname lookup aborted.';
    break;
   end;

   MyTreeNode:=ATreeNode.GetNextChild(MyTreeNode);
  until MyTreeNode=nil;
 end;
 Form1.URLStatBar.Position:=0;
end;

procedure TForm1.URLLexiconURLClick(Sender: TObject);
begin
 doLexiconOnURL(URLView.Selected);
end;

procedure TForm1.URLViewChange(Sender: TObject; Node: TTreeNode);
var
 typ      :Word;
begin
 typ:=GetURLNodeType(Node);
 if (typ=NODE_IS_IP) or (typ=NODE_IS_URL) then UpdateCatBoxes2(POURLEntry(Node.Data)^.FiltCat) else UpdateCatBoxes2(0);
end;

procedure TForm1.URLLexiconBranchClick(Sender: TObject);
var
 typ             :Byte;
begin
 typ:=GetURLNodeType(URLView.Selected);
 if typ=NODE_IS_IP then doLexiconOnIP(URLView.Selected);
 if typ=NODE_IS_NET then
 begin
   URLStatLbl1.Caption:='Number of hashes matched: 0';
   URLStatBar.Max:=StatisticsTotalURLHashes;
   URLStatBar.Position:=0;

   TheCancelButton.Visible:=True;
   TheCancelButton.Tag:=0;
   doLexiconOnNet(URLView.Selected);
   TheCancelButton.Visible:=False;
   TheCancelButton.Tag:=0;
 end;
end;

procedure TForm1.URLLexiconWholeTreeClick(Sender: TObject);
var
 MyTreeNode      :TTreeNode;

begin
 URLStatLbl1.Caption:='Number of hashes matched: 0';
 URLStatBar.Max:=StatisticsTotalURLHashes;
 URLStatBar.Position:=0;

 TheCancelButton.Visible:=True;
 TheCancelButton.Tag:=0;
{ MyTreeNode:=URLView.Items.GetFirstNode;}
 MyTreeNode:=URLView.Selected;
 if MyTreeNode<>nil then begin
  repeat
   if GetURLNodeType(MyTreeNode)=NODE_IS_NET then doLexiconOnNet(MyTreeNode);
   if Form1.TheCancelButton.Tag=-1 then break;
   MyTreeNode.Collapse(False);
   MyTreeNode:=MyTreeNode.getNextSibling;
  { MyTreeNode.Selected:=True;}
  until MyTreeNode=nil;
 end;
 TheCancelButton.Visible:=False;
 TheCancelButton.Tag:=0;
end;

procedure TForm1.TheCancelButtonClick(Sender: TObject);
begin
 TheCancelButton.tag:=-1;
end;

procedure TForm1.Exportdictionary1Click(Sender: TObject);
var
 T             :TextFile;
 s,outs        :String;
 inIP          :Boolean;
 i             :Integer;
 cnt           :LongInt;
 ATreeNode     :TTreeNode;

begin

 With SaveDialog1 do
 begin
  DefaultExt:='.txt';
  Filename:='cphack-dictionary';
  Filter:='Text documents (*.txt)|*.txt|All files (*.*)|*.*';
  InitialDir:=CfgDefaultPathEdit.Text;
  Title:='Choose a filename to export the dictionary to';
  if Execute then begin
   ProgressLabel.Caption:='Exporting dictionary ...';
   ProgressBar.Max:=URLView.Items.Count;
   Form1.Refresh;

   AssignFile(T,filename);
   ReWrite(T);

   cnt:=0;
   ATreeNode:=URLView.Items.GetFirstNode;
   While ATreeNode<>nil do
   begin
    if GetURLNodeType(ATreeNode)=NODE_IS_URL then
    begin
     s:=POURLEntry(ATreeNode.data)^.GetURL;
     outs:='';
     inIP:=false;
     for i:=1 to length(S) do
     begin
      if S[i]='/' then  { flush current word }
      begin
       if outs>'' then
       begin
       WriteLn(T,outs);
       outs:='';
       end;
      end else
       if S[i]='<' then inIP:=True else
        if S[i]='>' then inIP:=False else
         if not inIP then outs:=outs+S[i];
     end;
     if outs>'' then WriteLn(T,outs);
    end;
    ATreeNode:=ATreeNode.GetNext;
    Inc(cnt);
    if cnt mod 200=1 then ProgressBar.Position:=cnt;
   end;
   CloseFile(T);
   ProgressLabel.Caption:='Dictionary exported.';
   ProgressBar.Position:=0;

  end;
 end; { save dialog }

end;

procedure TForm1.URLLookupIPClick(Sender: TObject);
begin
 doLookupIP(URLView.Selected);
end;

procedure TForm1.URLLookupBranchClick(Sender: TObject);
var
 typ      :Byte;
 cnt      :LongInt;

begin
 typ:=GetURLNodeType(URLView.Selected);
 if typ=NODE_IS_NET then
 begin
   URLStatLbl1.Caption:='Number of IPs looked up: 0';
   URLStatBar.Max:=URLView.Items.Count-StatisticsTotalURLHashes; { approx. }
   URLStatBar.Position:=0;
   URLStatBox.Refresh;
   TheCancelButton.Visible:=True;
   TheCancelButton.Tag:=0;
   TheCancelButton.Refresh;
   cnt:=0;

   doLookupOnNet(URLView.Selected,cnt);

   URLStatBar.Position:=0;
   TheCancelButton.Visible:=False;
   TheCancelButton.Tag:=0;
 end;
end;

procedure TForm1.URLLookupforward1Click(Sender: TObject);
var
 MyTreeNode  :TTreeNode;
 cnt         :LongInt;

begin
 MyTreeNode:=URLView.Selected;

 URLStatLbl1.Caption:='Number of IPs looked up: 0';
 URLStatBar.Max:=URLView.Items.Count-StatisticsTotalURLHashes; { approx. }
 URLStatBar.Position:=0;
 URLStatBox.Refresh;
 TheCancelButton.Visible:=True;
 TheCancelButton.Tag:=0;
 TheCancelButton.Refresh;
 cnt:=0;

 if MyTreeNode<>nil then begin
  repeat
   if GetURLNodeType(MyTreeNode)=NODE_IS_NET then doLookupOnNet(MyTreeNode,cnt);
   if Form1.TheCancelButton.Tag=-1 then break;
   MyTreeNode.Collapse(False);
   MyTreeNode:=MyTreeNode.getNextSibling;
  until MyTreeNode=nil;
 end;

 URLStatBar.Position:=0;
 TheCancelButton.Visible:=False;
 TheCancelButton.Tag:=0;

end;


procedure TForm1.URLGotonextfullURL1Click(Sender: TObject);
var
 MyTreeNode      :TTreeNode;
 Cnt             :LongInt;

begin
 ProgressLabel.Caption:='Searching for next completed URL ...';
 ProgressLabel.Refresh;
 ProgressBar.Max:=URLView.Items.Count;
 Cnt:=0;
 MyTreeNode:=URLView.Selected.GetNext;
 if MyTreeNode<>nil then begin
  repeat
   if GetURLNodeType(MyTreeNode)=NODE_IS_URL then
    if POURLEntry(MyTreeNode.Data)^.isFullURL then
    begin
     MyTreeNode.Selected:=True;
     MyTreeNode.MakeVisible;
     Break;
    end;
   MyTreeNode:=MyTreeNode.getNext;
   Inc(cnt);
   if cnt mod 200=1 then ProgressBar.Position:=cnt;
  until MyTreeNode=nil;
 end;
 ProgressLabel.Caption:='';
 ProgressBar.Position:=0;
end;

procedure TForm1.ExportunresolvedIPs1Click(Sender: TObject);
var
 T               :TextFile;
 ATreeNode       :TTreeNode;
 hits,
 cnt             :LongInt;

begin
 With SaveDialog1 do
 begin
  DefaultExt:='txt';
  Filename:='unresolved-ips';
  Filter:='Text documents (*.txt)|*.txt|All files (*.*)|*.*';
  InitialDir:=CfgDefaultPathEdit.Text;
  Title:='Choose the file to save unresolved IPs to';
  if Execute then begin
    ProgressLabel.Caption:='Exporting unresolved IPs ...';
    ProgressBar.Max:=IPView.Items.Count+URLView.Items.Count;
    ProgressBar.Position:=0;
    ProgressLabel.Refresh;

    AssignFile(T,Filename);
    Rewrite(T);
    cnt:=0;
    hits:=0;
     ATreeNode:=IPView.Items.GetFirstNode;
     While ATreeNode<>nil do
     begin
      if Copy(ATreeNode.Text,Length(ATreeNode.Text),1)<>')' then
      begin
       WriteLn(T,ATreeNode.Text);
       Inc(hits);
      end;
      ATreeNode:=ATreeNode.GetNext;
      inc(cnt);
      if cnt mod 200=1 then ProgressBar.Position:=cnt;
     end;

     ATreeNode:=URLView.Items.GetFirstNode;
     While ATreeNode<>nil do
     begin
      if (GetURLNodeType(ATreeNode)=NODE_IS_IP) and (Copy(ATreeNode.Text,Length(ATreeNode.Text),1)<>')') then
      begin
       WriteLn(T,ATreeNode.Text);
       Inc(hits);
      end;
      ATreeNode:=ATreeNode.GetNext;
      inc(cnt);
      if cnt mod 200=1 then ProgressBar.Position:=cnt;
     end;
    CloseFile(T);

    ProgressLabel.Caption:=St(hits)+' unresolved IPs exported.';
    ProgressBar.Position:=0;

  end; { execute ..}
 end; { with savedialog }
end;

procedure TForm1.ExportURLhashes1Click(Sender: TObject);
var
 T               :TextFile;
 ATreeNode       :TTreeNode;

 i,
 hits,
 cnt             :LongInt;
 s,outs          :String;
 inHash          :Boolean;

begin
 With SaveDialog1 do
 begin
  DefaultExt:='txt';
  Filename:='unresolved-hashes';
  Filter:='Text documents (*.txt)|*.txt|All files (*.*)|*.*';
  InitialDir:=CfgDefaultPathEdit.Text;
  Title:='Choose the file to save unresolved hashes to';
  if Execute then begin
    ProgressLabel.Caption:='Exporting unresolved hashes ...';
    ProgressBar.Max:=URLView.Items.Count;
    ProgressBar.Position:=0;
    ProgressLabel.Refresh;

    AssignFile(T,Filename);
    Rewrite(T);
    cnt:=0;
    hits:=0;

     ATreeNode:=URLView.Items.GetFirstNode;
     While ATreeNode<>nil do
     begin
      if (GetURLNodeType(ATreeNode)=NODE_IS_URL) then
      begin
       s:=POURLEntry(ATreeNode.data)^.GetURL;
       outs:='';
       inHash:=false;
       for i:=1 to length(S) do
       begin
        if S[i]='/' then  { flush current word }
        begin
         if outs>'' then
         begin
          WriteLn(T,outs);
          outs:='';
          inc(hits);
         end;
        end else
         if S[i]='<' then inHash:=True else
          if S[i]='>' then inHash:=False else
           if inHash then outs:=outs+S[i];
       end;
       if outs>'' then begin WriteLn(T,outs); inc(hits); end;
      end;
      ATreeNode:=ATreeNode.GetNext;
      inc(cnt);
      if cnt mod 200=1 then ProgressBar.Position:=cnt;
     end;
    CloseFile(T);

    ProgressLabel.Caption:=St(hits)+' unresolved hashes exported.';
    ProgressBar.Position:=0;

  end; { execute ..}
 end; { with savedialog }
end;

procedure TForm1.MyHomepageClick(Sender: TObject);
begin
 ShellExecute(Application.handle, nil, Pchar('http://hem.passagen.se/eddy1/reveng/'), nil, nil, SW_SHOWNORMAL);
end;

procedure TForm1.SearchButtonClick(Sender: TObject);
var
 ATreeNode         :TTreeNode;
begin
 if (SearchEdit.Text='') or (URLView.Items.Count<1) then EXIT;

 ProgressLabel.Caption:='Searching ...';
 ProgressLabel.Refresh;

 SearchBarEnabled(FALSE);
 if URLView.Selected=nil then
  ATreeNode:=URLView.Items.GetFirstNode
   else
  ATreeNode:=URLView.Selected.GetNext; { continue search }
 While ATreeNode<>nil do
 begin
  if pos(SearchEdit.Text,ATreeNode.Text)>0 then
  begin
   ATreeNode.MakeVisible;
   ATreeNode.Selected:=True;
   URLView.SetFocus;
   ProgressLabel.Caption:='Text found.';
   SearchBarEnabled(TRUE);
   EXIT;
  end;
  ATreeNode:=ATreeNode.GetNext;
 end;
 SearchBarEnabled(TRUE);
 ProgressLabel.Caption:='Text not found.';
end;

procedure TForm1.SearchEditKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
 if Key=13 then SearchButtonClick(Sender);
end;

Procedure FindNextCategory(const catbit: Word);
var
 ATreeNode         :TTreeNode;
begin
 Form1.ProgressLabel.Caption:='Searching ...';
 Form1.ProgressLabel.Refresh;

 SearchBarEnabled(FALSE);
 if Form1.URLView.Selected=nil then
  ATreeNode:=Form1.URLView.Items.GetFirstNode
   else
  ATreeNode:=Form1.URLView.Selected.GetNext; { continue search }
 While ATreeNode<>nil do
 begin

  if (ATreeNode.Level>0) and ((POURLEntry(ATreeNode.Data)^.FiltCat AND CatBit)=CatBit) then
  begin
   ATreeNode.MakeVisible;
   ATreeNode.Selected:=True;
   Form1.URLView.SetFocus;
   Form1.ProgressLabel.Caption:='Category found.';
   SearchBarEnabled(TRUE);
   EXIT;
  end;
  ATreeNode:=ATreeNode.GetNext;
 end;
 SearchBarEnabled(TRUE);
 Form1.ProgressLabel.Caption:='Category not found.';
end;

procedure TForm1.LocateCatByTag(Sender: TObject);
begin
 FindNextCategory(TMenuItem(Sender).Tag);
end;


{ smarter dictionary attack below }

Procedure NOTBufferToList(const HeaderEntry: PTNOTHeaderEntry;var HashList: TList);
var
 AByteArray     :PByteArray;
 i              :LongInt;
 theCat         :Word;
 theSubLen,b    :Byte;
 thePtr         :Pointer;

begin

  AByteArray:=Addr(NOTBuf^[HeaderEntry^.Offset]);
  i:=2; { skip 'SD' start }
  repeat
   theCat:=Word(AByteArray^[i+5] shl 8)+AByteArray^[i+4];
   Inc(i,6);
   if theCat=0 then { parse sub-header }
   begin
    repeat
     theSubLen:=AByteArray^[i];
     Inc(i,3);
     for b:=1 to ((theSubLen-3) div 4) do { add hashes }
     begin
      Move(AByteArray^[i],thePtr,4);
      HashList.Add(thePtr); { note: fake pointer! is really hash. }
      Inc(i,4);
     end;
    until AByteArray^[i]=0;
    Inc(i); { skip terminator }
   end;
  until i>(HeaderEntry^.Size-3);

end;

function SmartDicSortFunc(Item1, Item2: Pointer): Integer;
begin
 if cardinal(item1)<cardinal(item2) then result:=-1 else
  if cardinal(item1)>cardinal(item2) then result:=1 else result:=0;
end;

procedure TForm1.Smartdictionaryattack1Click(Sender: TObject);
var
 HashList    :TList;
 T           :TextFile;
 hits        :LongInt;
 lex         :POLexicon;
 hit         :Boolean;
 outfilefn,
 s           :String;

 { for binary search }
 l,u,i,N     :LongInt;
 Ki          :Cardinal;

 Function isInList(const K: Cardinal): Boolean; { binary search }
 begin
  result:=false;
  l:=1;
  u:=N;

  while u>=l do
  begin
   i:=(l+u) div 2;
   Ki:=Cardinal(HashList.Items[i-1]);
   if K<Ki then u:=i-1 else
    if K>Ki then l:=i+1 else
    begin
     result:=true;
     break;
    end;
  end;
 end; { // isInList }

begin

 With SaveDialog1 do
 begin
  DefaultExt:='.txt';
  Filename:='cphack-dictionary';
  Filter:='Text documents (*.txt)|*.txt|All files (*.*)|*.*';
  InitialDir:=CfgDefaultPathEdit.Text;
  Title:='Select output dictionary (will append if exists)';
  if not Execute then
  begin
   ProgressLabel.Caption:='Smart dictionary attack aborted.';
   EXIT;
  end;
   outfilefn:=Filename;
   if outfilefn=Form1.CfgLexiconEdit.Text then
   begin
    ProgressLabel.Caption:='Cannot use source dictionary for output!';
    EXIT;
   end;
 end;

 { check if we must load the buffer }
 if NOTBuf=nil then
 if not doAskLoadDecryptNOTFile then
 begin
  ProgressLabel.Caption:='Smart dictionary attack aborted.';
  exit;
 end;
 ProgressLabel.Caption:='Smart dictionary attack initialization ...';
 Form1.Refresh;

 HashList:=TList.Create;

 { First, import hashes into the list }
 CurrNOTTbl:=0; { reset counter }
 NOTHeaderEntry:=GetNextTableID;
 while NOTHeaderEntry<>nil do
 begin
  case NOTHeaderEntry^.TblType of
   $49,$4F: NOTBufferToList(NOTHeaderEntry,HashList);
  end;
  NOTHeaderEntry:=GetNextTableID;
 end;

 { Sort the pointers }
 HashList.Sort(SmartDicSortFunc);

 { Remove duplicates }
 i:=0;
 repeat
  if HashList.list[i]=HashList[i+1] then
   HashList.Delete(i)
  else
   inc(i);
 until i=HashList.Count-1;

 AssignFile(T,outfilefn);
 if FileExists(outfilefn) then Append(T) else ReWrite(T);

 New(lex,Create);
 lex.SetFileLexicon(Form1.CfgLexiconEdit.Text);
 if Form1.CfgPrefixEnabled.Checked then
  lex.PrefixActive(lex.SetFilePrefix(Form1.CfgPrefixEdit.Text));
 if Form1.CfgSuffixEnabled.Checked then
  lex.SuffixActive(lex.SetFileSuffix(Form1.CfgSuffixEdit.Text));
 lex.Reset;

 hits:=0;
 N:=HashList.Count;

 Form1.PageControl1.Activepage:=Form1.TabSheet3;
 Form1.ProgressBar.Max:=lex.lexmax;
 Form1.ProgressBar.Position:=0;
 Form1.URLStatLbl1.Caption:='Number of hashes matched: 0 / '+St(N);
 Form1.URLStatBar.Max:=N;
 Form1.URLStatBar.Position:=0;
 Form1.ProgressLabel.Caption:='Smart dictionary attack in progress ...';
 Form1.Refresh;

 While lex.GetNextWord(s) do
 begin
  { check H(s) against list of hashes }
  hit:=isInList(CRC32String(s));

  if hit then
  begin
   Inc(hits);
   WriteLn(T,s);
   Form1.URLStatLbl1.Caption:='Number of hashes matched: '+St(hits)+' / '+St(N);
   Form1.URLStatBar.Position:=hits;
   Form1.URLStatLbl1.Refresh;
  end;

  if lex.lexposition mod 400=1 then
  begin
   Form1.ProgressBar.Position:=lex.lexposition;
   Application.ProcessMessages;
  end;

 end;
 Form1.ProgressBar.Position:=0;
 Form1.ProgressLabel.Caption:='Smart dictionary attack completed.';

 CloseFile(T);
 Dispose(lex,Destroy);
 HashList.Free;

end;

end.


