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

 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: Dagsta

 Dagsta handling

*********************************************************}
unit Dagsta;

interface

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

procedure InitDagsta;
procedure CheckDagsta;
procedure DisconnectDagsta(Socket_Error: Integer);
procedure ConnectDagsta;

implementation

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

type
  TDagstaThread = 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: TDagstaThread;

constructor TDagstaThread.Create;
begin
  Socket := Dagsta_Socket;
  Dagsta_Salt := '';
  Napi_Sync := GetTickCount;
  Napi_Sync_Stats := Napi_Sync;
  Last_Error := 0;
  inherited Create(False);
end;

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

procedure TDagstaThread.Execute;
var
  List: TMyStringList;
begin
  FreeOnTerminate := True;
  List := TMyStringList.Create;
  ResolveNameToIp(Dagsta_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(Dagsta_Socket);
      Dagsta_Socket := INVALID_SOCKET;
    except
    end;
    Exit;
  end;
  TCPSocket_Connect(Socket, Ip, Dagsta_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(Dagsta_Socket);
      Dagsta_Socket := INVALID_SOCKET;
    except
    end;
    Exit;
  end;
  TCPSocket_Block(Socket, False);
  Synchronize(SyncConnected);
end;

procedure TDagstaThread.SyncConnected;
begin
  if not Running then Exit;
  if not Log_Dagsta then Exit;
  Log(slDagsta, GetLangE(LNG_DAGSTA_CONNECTED));
end;

procedure TDagstaThread.SyncIP;
begin
  if not Running then Exit;
  if not Log_Dagsta then Exit;
  if Ip = '' then
    Log(slDagsta, GetLangE(LNG_DAGSTA_NORESOLVE, Dagsta_Host))
  else
    Log(slDagsta, GetLangE(LNG_DAGSTA_RESOLVED, Dagsta_Host, Ip));
end;

procedure TDagstaThread.SyncNoConnect;
begin
  if not Running then Exit;
  if not Log_Dagsta then Exit;
  Log(slDagsta, GetLangE(LNG_DAGSTA_NOCONNECT, GetErrorDesc(Last_Error)));
end;

procedure DisconnectDagsta(Socket_Error: Integer);
begin
  if Napi_Thread <> nil then
  try
    Napi_Thread.Terminate;
    Napi_Thread := nil;
    Dagsta_Socket := INVALID_SOCKET;
  except
  end;
  if Dagsta_Socket <> INVALID_SOCKET then
  begin
    if Log_Dagsta then
      Log(slDagsta, GetLangT(LNG_DAGSTA_DISCONNECT));
    DoCloseSocket(Dagsta_Socket);
    Dagsta_Socket := INVALID_SOCKET;
  end;
  Dagsta_Salt := '';
end;

procedure ConnectDagsta;
begin
  if Dagsta_User = '' then
    Dagsta_Enabled := False;
  if Dagsta_Password = '' then
    Dagsta_Enabled := False;
  if Dagsta_Host = '' then
    Dagsta_Enabled := False;
  if Dagsta_Port = '' then
    Dagsta_Enabled := False;
  if not Dagsta_Enabled then Exit;
  if Napi_Thread <> nil then Exit;
  if Dagsta_Socket <> INVALID_SOCKET then
    DoCloseSocket(Dagsta_Socket);
  Dagsta_Socket := SynSock.Socket(PF_INET, Integer(SOCK_StrEAM), IPPROTO_TCP);
  Inc(Sockets_Count);
  if Log_Dagsta then
    Log(slDagsta, GetLangT(LNG_DAGSTA_CONNECTING));
  Napi_Thread := TDagstaThread.Create;
end;

procedure InitDagsta;
begin
  Napi_Sync := GetTickCount;
  Napi_Sync_Stats := Napi_Sync;
  Dagsta_Salt := '';
  Napi_Thread := nil;
  if Dagsta_User = '' then
    Dagsta_Enabled := False;
  if Dagsta_Password = '' then
    Dagsta_Enabled := False;
  if Dagsta_Host = '' then
    Dagsta_Enabled := False;
  if Dagsta_Port = '' then
    Dagsta_Enabled := False;
  if not Dagsta_Enabled then Exit;
  ConnectDagsta;
end;

function WriteDagsta(Str: string; Show_Log: Boolean = True): Boolean;
var
  Last_Error: Integer;
begin
  Result := False;
  if Dagsta_Socket = INVALID_SOCKET then Exit;
  TCPSocket_SendString(Dagsta_Socket, Str + #13#10, Last_Error);
  if Last_Error = WSAEWOULDBLOCK then Exit;
  if Last_Error <> 0 then
  begin
    DisconnectDagsta(Last_Error);
    Exit;
  end;
  Inc(Bytes_Out, Length(Str) + 2);
  if Log_Dagsta and Show_Log then
    Log(slDagsta, GetLangT(LNG_DAGSTA_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';
  WriteDagsta(Str);
end;

procedure DagstaReceive;
var
  Str: string;
  Buf: array[0..1024] of Char;
  I: Integer;
  List: TMyStringList;
  Addr: string;
  Last_Error: Integer;
begin
  if Dagsta_Socket = INVALID_SOCKET then Exit;
  Last_Error := 0;
  I := TCPSocket_RecvBuffer(Dagsta_Socket, PChar(@Buf[0]), 1024, Last_Error);
  if Last_Error = WSAEWOULDBLOCK then Exit;
  if Last_Error <> 0 then
  begin
    DisconnectDagsta(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_Dagsta then
      Log(slDagsta, GetLangT(LNG_DAGSTA_ERROR, Str));
    DisconnectDagsta(0);
    Exit;
  end;
  case I of
    220:
      begin
        if Log_Dagsta then
          Log(slDagsta, GetLangT(LNG_DAGSTA_REPLY, Str));
        if List.Count > 1 then
          Dagsta_Salt := List.Strings[1]
        else
          Dagsta_Salt := ' ';
        Str := 'USER ' + Dagsta_User;
        WriteDagsta(Str);
      end;
    221:
      begin
        if Log_Dagsta then
          Log(slDagsta, GetLangT(LNG_DAGSTA_REPLY, Str));
        DisconnectDagsta(0);
      end;
    300:
      begin
        if Log_Dagsta then
          Log(slDagsta, GetLangT(LNG_DAGSTA_REPLY, Str));
        Str := 'PASS ' + UpperCase(StrMD5(Trim(Dagsta_Salt) +
          Trim(Dagsta_Password)));
        if WriteDagsta(Str, False) then
          if Log_Dagsta then
            Log(slDagsta, GetLangT(LNG_DAGSTA_SEND, 'PASS *****'));
      end;
    201:
      begin
        if Log_Dagsta then
          Log(slDagsta, GetLangT(LNG_DAGSTA_REPLY, Str));
        List.Clear;
        if Trim(Dagsta_Myip) = '' then
          ResolveNameToIP(ServerName_T, List)
        else
          List.Add(Dagsta_Myip);
        if List.Count = 0 then
          Addr := ''
        else
          Addr := List.Strings[0];
        if Addr = '' then
        begin
          if Log_Dagsta then
            Log(slDagsta, GetLangT(LNG_DAGSTA_NORESOLVE, ServerName_T));
          Addr := '127.0.0.1';
        end;
        Str := ServerName_T + ' ' + Addr;
        Str := 'IPPORT ' + Str + ' ' + IntToStr(Dagsta_MyPort);
        if WriteDagsta(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;
          WriteDagsta(Str);
        end;
      end;
    200:
      begin
        if Log_Dagsta then
          Log(slDagsta, GetLangT(LNG_DAGSTA_REPLY, '200 Stats updated.'));
      end;
    400..1000:
      begin
        if Log_Dagsta then
          Log(slDagsta, GetLangT(LNG_DAGSTA_REPLY, Str));
        WriteDagsta('QUIT');
      end;
  else
    if (I < 0) or (I > 1000) then
      DisconnectDagsta(0);
  end;
  FreeStringList(List);
end;

procedure CheckDagsta;
var
  Last_Error: Integer;
begin
  try
    if Napi_Thread <> nil then Exit;
    if Dagsta_Socket <> INVALID_SOCKET then
    begin
      if TCPSocket_CanRead(Dagsta_Socket, 0, Last_Error) then
        DagstaReceive
      else if Last_Error <> 0 then
        DisconnectDagsta(Last_Error);
    end;
    if (GetTickCount - Napi_Sync) > 60000 then
    begin
      if Dagsta_Socket <> INVALID_SOCKET then
      begin
        if Dagsta_Salt = '' then
        begin
          if Log_Dagsta then
            Log(slDagsta, GetLangT(LNG_DAGSTA_NOWELCOME));
          DisconnectDagsta(0);
        end
        else if Dagsta_Autodisconnect and (Local_Users >= Max_Users) then
        begin
          if Log_Dagsta then
            Log(slDagsta, GetLangT(LNG_DAGSTA_AUTODISCONNECT));
          DisconnectDagsta(0);
        end;
      end;
      if Dagsta_Socket = INVALID_SOCKET then
      begin
        if (not Dagsta_Autodisconnect) or (Local_Users < (Max_Users - 20)) then
          if not Network_Hub then
            ConnectDagsta;
      end
      else
        Napi_Sync := GetTickCount;
    end;
    if (GetTickCount - Napi_Sync_Stats) > Dagsta_Delay then
      WriteStats;
  except
    on E: Exception do
      DebugLog('Exception in CheckDagsta : ' + E.Message);
  end;
end;

end.
