{*********************************************************

 SlavaNap source code.

 Copyright 2001,2002 by SlavaNap development team
 Released under GNU General Public License

 Latest version is available at
 http://www.slavanap.org

**********************************************************

 Unit: Napigator

 Napigator handling

*********************************************************}
unit Napigator;

interface

uses
  SysUtils, Classes, Classes2, WinSock, Windows, BlckSock, SynSock;

procedure InitNapigator;
procedure CheckNapigator;
procedure DisconnectNapigator(Socket_Error: Integer);
procedure ConnectNapigator;

implementation

uses
  Vars, Thread, STypes, Constants, Lang, Md5, Memory_Manager, Handler;

type
  TNapigatorThread = class(TThread)
    Ip: string;
    Socket: HSocket;
    Last_Error: Integer;
    constructor Create;
    destructor Destroy; override;
    procedure Execute; override;
    procedure SyncIP;
    procedure SyncNoConnect;
    procedure SyncConnected;
  end;

var
  Napi_Sync, Napi_Sync_Stats: Cardinal;
  Napi_Thread: TNapigatorThread;

constructor TNapigatorThread.Create;
begin
  Socket := Napigator_Socket;
  Napigator_Salt := '';
  Napi_Sync := GetTickCount;
  Napi_Sync_Stats := Napi_Sync;
  Last_Error := 0;
  inherited Create(False);
end;

destructor TNapigatorThread.Destroy;
begin
  if Napi_Thread = nil then
  try
    if Napigator_Socket <> INVALID_SOCKET then
    begin
      DoCloseSocket(Napigator_Socket);
      Napigator_Socket := INVALID_SOCKET;
    end;
  except
  end;
  Napi_Thread := nil;
  inherited Destroy;
end;

procedure TNapigatorThread.Execute;
var
  List: TMyStringList;
begin
  FreeOnTerminate := True;
  List := TMyStringList.Create;
  ResolveNameToIp(Napigator_Host, List);
  if List.Count > 0 then
    Ip := List.Strings[0]
  else
    Ip := '';
  List.Free;
  if Terminated then Exit;
  Synchronize(SyncIP);
  if Ip = '' then
  begin
    if not Terminated then
    try
      Socket := INVALID_SOCKET;
      DoCloseSocket(Napigator_Socket);
      Napigator_Socket := INVALID_SOCKET;
    except
    end;
    Exit;
  end;
  TCPSocket_Connect(Socket, Ip, Napigator_Port, Last_Error);
  if not Running then Exit;
  Napi_Sync := GetTickCount;
  Napi_Sync_Stats := Napi_Sync;
  if Last_Error <> 0 then
  begin
    Synchronize(SyncNoConnect);
    if not Terminated then
    try
      Socket := INVALID_SOCKET;
      DoCloseSocket(Napigator_Socket);
      Napigator_Socket := INVALID_SOCKET;
    except
    end;
    Exit;
  end;
  TCPSocket_Block(Socket, False);
  Synchronize(SyncConnected);
end;

procedure TNapigatorThread.SyncConnected;
begin
  if not Running then Exit;
  if not Log_Napigator then Exit;
  Log(slNapigator, GetLangE(LNG_NAPIGATOR_CONNECTED));
end;

procedure TNapigatorThread.SyncIP;
begin
  if not Running then Exit;
  if not Log_Napigator then Exit;
  if Ip = '' then
    Log(slNapigator, GetLangE(LNG_NAPIGATOR_NORESOLVE, Napigator_Host))
  else
    Log(slNapigator, GetLangE(LNG_NAPIGATOR_RESOLVED, Napigator_Host, Ip));
end;

procedure TNapigatorThread.SyncNoConnect;
begin
  if not Running then Exit;
  if not Log_Napigator then Exit;
  Log(slNapigator, GetLangE(LNG_NAPIGATOR_NOCONNECT, GetErrorDesc(Last_Error)));
end;

procedure DisconnectNapigator(Socket_Error: Integer);
begin
  if Napi_Thread <> nil then
  try
    Napi_Thread.Terminate;
    Napi_Thread := nil;
    Napigator_Socket := INVALID_SOCKET;
  except
  end;
  if Napigator_Socket <> INVALID_SOCKET then
  begin
    if Log_Napigator then
      Log(slNapigator, GetLangT(LNG_NAPIGATOR_DISCONNECT));
    DoCloseSocket(Napigator_Socket);
    Napigator_Socket := INVALID_SOCKET;
  end;
  Napigator_Salt := '';
end;

procedure ConnectNapigator;
begin
  if Napigator_User = '' then
    Napigator_Enabled := False;
  if Napigator_Password = '' then
    Napigator_Enabled := False;
  if Napigator_Host = '' then
    Napigator_Enabled := False;
  if Napigator_Port = '' then
    Napigator_Enabled := False;
  if not Napigator_Enabled then Exit;
  if Napi_Thread <> nil then Exit;
  if Napigator_Socket <> INVALID_SOCKET then
    DoCloseSocket(Napigator_Socket);
  Napigator_Socket := SynSock.Socket(PF_INET, Integer(SOCK_StrEAM),
    IPPROTO_TCP);
  Inc(Sockets_Count);
  if Log_Napigator then
    Log(slNapigator, GetLangT(LNG_NAPIGATOR_CONNECTING));
  Napi_Thread := TNapigatorThread.Create;
end;

procedure InitNapigator;
begin
  Napi_Sync := GetTickCount;
  Napi_Sync_Stats := Napi_Sync;
  Napigator_Salt := '';
  Napi_Thread := nil;
  if Napigator_User = '' then
    Napigator_Enabled := False;
  if Napigator_Password = '' then
    Napigator_Enabled := False;
  if Napigator_Host = '' then
    Napigator_Enabled := False;
  if Napigator_Port = '' then
    Napigator_Enabled := False;
  if not Napigator_Enabled then Exit;
  ConnectNapigator;
end;

function WriteNapigator(Str: string; Show_Log: Boolean = True): Boolean;
var
  Last_Error: Integer;
begin
  Result := False;
  if Napigator_Socket = INVALID_SOCKET then Exit;
  TCPSocket_SendString(Napigator_Socket, Str + #13#10, Last_Error);
  if Last_Error = WSAEWOULDBLOCK then Exit;
  if Last_Error <> 0 then
  begin
    DisconnectNapigator(Last_Error);
    Exit;
  end;
  Inc(Bytes_Out, Length(Str) + 2);
  if Log_Napigator and Show_Log then
    Log(slNapigator, GetLangT(LNG_NAPIGATOR_SEND, Str));
  Result := True;
end;

procedure WriteStats;
var
  Str: string;
begin
  Napi_Sync_Stats := GetTickCount;
  Str := 'STATS ' + ServerName_T + ' ' + IntToStr(Total_Users) + ' ' +
    IntToStr(Total_Files) + ' 0 ' + IntToStr(Total_Bytes) + ' 0';
  WriteNapigator(Str);
end;

procedure NapigatorReceive;
var
  Str: string;
  Buf: array[0..1024] of Char;
  I: Integer;
  List: TMyStringList;
  Addr: string;
  Last_Error: Integer;
begin
  if Napigator_Socket = INVALID_SOCKET then Exit;
  Last_Error := 0;
  I := TCPSocket_RecvBuffer(Napigator_Socket, PChar(@Buf[0]), 1024, Last_Error);
  if Last_Error = WSAEWOULDBLOCK then Exit;
  if Last_Error <> 0 then
  begin
    DisconnectNapigator(Last_Error);
    Exit;
  end;
  if I = 0 then Exit;
  Inc(Bytes_in, I);
  SetLength(Str, I);
  Move(Buf[0], Str[1], I);
  for I := 1 to Length(Str) do
    if Str[I] < #32 then
      Str[I] := #32;
  List := CreateStringList;
  SplitString(Trim(Str), List);
  if List.Count < 1 then
  begin // Weird error
    FreeStringList(List);
    Exit;
  end;
  I := StrToIntDef(List.Strings[0], -1);
  if I = -1 then
  begin // Weird error - Disconnecting
    FreeStringList(List);
    if Log_Napigator then
      Log(slNapigator, GetLangT(LNG_NAPIGATOR_ERROR, Str));
    DisconnectNapigator(0);
    Exit;
  end;
  case I of
    220:
      begin
        if Log_Napigator then
          Log(slNapigator, GetLangT(LNG_NAPIGATOR_REPLY, Str));
        if List.Count > 1 then
          Napigator_Salt := List.Strings[1]
        else
          Napigator_Salt := ' ';
        Str := 'USER ' + Napigator_User;
        WriteNapigator(Str);
      end;
    221:
      begin
        if Log_Napigator then
          Log(slNapigator, GetLangT(LNG_NAPIGATOR_REPLY, Str));
        DisconnectNapigator(0);
      end;
    300:
      begin
        if Log_Napigator then
          Log(slNapigator, GetLangT(LNG_NAPIGATOR_REPLY, Str));
        Str := 'PASS ' + UpperCase(StrMD5(Trim(Napigator_Salt) +
          Trim(Napigator_Password)));
        if WriteNapigator(Str, False) then
          if Log_Napigator then
            Log(slNapigator, GetLangT(LNG_NAPIGATOR_SEND, 'PASS *****'));
      end;
    201:
      begin
        if Log_Napigator then
          Log(slNapigator, GetLangT(LNG_NAPIGATOR_REPLY, Str));
        List.Clear;
        if Trim(Napigator_Myip) = '' then
          ResolveNameToIP(ServerName_T, List)
        else
          List.Add(Napigator_Myip);
        if List.Count = 0 then
          Addr := ''
        else
          Addr := List.Strings[0];
        if Addr = '' then
        begin
          if Log_Napigator then
            Log(slNapigator, GetLangT(LNG_NAPIGATOR_NORESOLVE, ServerName_T));
          Addr := '127.0.0.1';
        end;
        if Napigator_Fake_Ip then
          Addr := Encode_Ip_Rev(Addr);
        Str := ServerName_T + ' ' + Addr;
        Str := 'IPPORT ' + Str + ' ' + IntToStr(Napigator_MyPort);
        if WriteNapigator(Str) then
        begin
          Str := 'STATS ' + ServerName_T + ' ' + IntToStr(Total_Users) + ' ' +
            IntToStr(Total_Files) + ' 0 ' + IntToStr(Total_Bytes) + ' 0';
          Napi_Sync := GetTickCount;
          Napi_Sync_Stats := Napi_Sync;
          WriteNapigator(Str);
        end;
      end;
    200:
      begin
        if Log_Napigator then
          Log(slNapigator, GetLangT(LNG_NAPIGATOR_REPLY, '200 Stats updated.'));
      end;
    400..1000:
      begin
        if Log_Napigator then
          Log(slNapigator, GetLangT(LNG_NAPIGATOR_REPLY, Str));
        WriteNapigator('QUIT');
      end;
  else
    if (I < 0) or (I > 1000) then
      DisconnectNapigator(0);
  end;
  FreeStringList(List);
end;

procedure CheckNapigator;
var
  Last_Error: Integer;
begin
  try
    if Napi_Thread <> nil then Exit;
    if Napigator_Socket <> INVALID_SOCKET then
    begin
      if TCPSocket_CanRead(Napigator_Socket, 0, Last_Error) then
        NapigatorReceive
      else if Last_Error <> 0 then
        DisconnectNapigator(Last_Error);
    end;
    if (GetTickCount - Napi_Sync) > 60000 then
    begin
      if Napigator_Socket <> INVALID_SOCKET then
      begin
        if Napigator_Salt = '' then
        begin
          if Log_Napigator then
            Log(slNapigator, GetLangT(LNG_NAPIGATOR_NOWELCOME));
          DisconnectNapigator(0);
        end
        else if Napigator_Autodisconnect and (Local_Users >= Max_Users) then
        begin
          if Log_Napigator then
            Log(slNapigator, GetLangT(LNG_NAPIGATOR_AUTODISCONNECT));
          DisconnectNapigator(0);
        end;
      end;
      if Napigator_Socket = INVALID_SOCKET then
      begin
        if (not Napigator_Autodisconnect) or (Local_Users < (Max_Users - 20))
          then
          if not Network_Hub then
            ConnectNapigator;
      end
      else
        Napi_Sync := GetTickCount;
    end;
    if (GetTickCount - Napi_Sync_Stats) > Napigator_Delay then
      WriteStats;
  except
    on E: Exception do
      DebugLog('Exception in CheckNapigator : ' + E.Message);
  end;
end;

end.
