Author Topic: Exploiting a Soldat Server as TCP Socket Server  (Read 1198 times)

0 Members and 1 Guest are viewing this topic.

Offline CurryWurst

  • Camper
  • ***
  • Posts: 265
    • Soldat Global Account System
Exploiting a Soldat Server as TCP Socket Server
« on: September 02, 2009, 09:24:37 am »
The last couple of days I played around with the script core socket functions and the TCP admin port.
Actually I wanted to create a socket accepting incoming connections to a Soldat server, but eventually, after talking to EnEsCe and doing some test, I realized it is impossible to allow incoming connections via sockets. Hence, I had to think about a new idea, which resulted into using the admin port as TCP socket server, but unfortunately they are hard to deal with: Obviously, you are not able to identify sockets connected to the admin port by an id or sth. unique and before some nerds now start to complain that there are IPs to identify a socket, I ask you what happens, when there are multiple sockets connected to the admin port from the same IP??? - Correct, there's no way to determine which socket sent a message, or to which socket I want to send data. Nevertheless, you can write your own socket identification system by assigning an unique id to each connected socket and every package sent via them. Responses from the TCP socket server have to be "labeled" with these unique ids, so sockets can determine if a package is addressed to them. But what happens if a server disconnects and you exactly need to know which socket it was? - OnAdminDisconnect will be called with the IP of the socket which disconnected. Hmmm IPs are not unique and in order to properly disconnect a server and to know which socket disconnected, we need to ping each server with the IP which just disconnected.

Basically, that's how you can use the admin port as TCP socket server. I provided a little demonstration below. It is about a "semi-global account system" which makes use of this trick to provide a "global account database" for different servers. Unfortunately, I haven't got around to write the client script, hence you can only test this demo with putty etc. Please note that this script is only for demonstration purposes and that it is coded in a very bad way. If you want to test the script, send me a pm and I'll try to explain how you can fake a client connecting to TCP server.

The database script used by the server:
Code: [Select]
const
  MySSQL_PATH = './tables/';

type
  TMySSQL_Column = record
    Offset: word;
    Value: string;
  end;

  TMySSQL_Row = record
    Columns: array of TMySSQL_Column;
    Data: string;
    Cached: boolean;
  end;
 
  TMySSQL_Table = record
    Rows: array of TMySSQL_Row;
    Name, Exception: string;
    Modified, Linked: boolean;
  end;

function GetTypeOF(Value: variant): string;
begin
  case VarType(Value) of
    3  : Result:= IntToStr(Value);
    5  : Result:= FloatToStr(Value);
    11 : Result:= iif(Value, 'true', 'false');
    256: Result:= Value;
  end;
end;

function AryVntToAryStr(const AryVnt: array of variant): array of string;
var
  i: integer;
begin
  Result:= [];
  SetArrayLength(Result, GetArrayLength(AryVnt));
  for i:= 0 to GetArrayLength(Result) - 1 do
  begin
    Result[i]:= AryVnt[i];
  end;
end;

function Implode(const Source: array of string; const Delimiter: string): string;
var
  i: integer;
begin
  Result:= '';
  for i:= 0 to GetArrayLength(Source) - 1 do
  begin
    Result:= Result + Source[i] + Delimiter;
  end;
end;

function ExplodeRow(Source: string; const Delimiter: string): array of TMySSQL_Row;
var
  x, y, l: integer;
  b: word;
begin
  l:= length(Delimiter);
  b:= length(Source);
  Source:= Source + Delimiter;
  Result:= [];
  repeat
    x:= Pos(Delimiter, Source);
    SetArrayLength(Result, y + 1);
    Result[y].Data:= Copy(Source, 1, x - 1);
    y:= y + 1;
    Delete(Source, 1, x + l - 1);
  until (x = 0);
  SetArrayLength(Result, iif(b = 0, y - 2,  y - 1));
end;

function ReadFromFile(const Path, Filename: string): string;
begin
  Result:= ReadFile(Path + Filename);
  Result:= Copy(Result, 0, length(Result) - 2);
end;

function RowExists(const Table: TMySSQL_Table; const Row: word): boolean;
begin
  Result:= GetArrayLength(Table.Rows) - 1 >= Row;
end;

function ColumnCached(var Table: TMySSQL_Table; const Row, Column: word): boolean;
begin
  if (RowExists(Table, Row)) then
  begin
    Result:= GetArrayLength(Table.Rows[Row].Columns) - 1 >= Column;
  end;
end;

procedure OnErrorOccur(var Table: TMySSQL_Table; const Method, Message: string);
begin
  Table.Exception:= ' [*] [Error] MySSQL -> (' + Method + '): ' + Message;
end;

function DumpCacheRow(var Table: TMySSQL_Table; const Row: word): boolean;
var
  Cache: string;
  i: integer;
begin
  if (RowExists(Table, Row)) then
  begin
    if (Table.Rows[Row].Cached) then
    begin
      Cache:= '';
      for i:= 0 to GetArrayLength(Table.Rows[Row].Columns) - 1 do
      begin
        Cache:= Cache + Table.Rows[Row].Columns[i].Value + #9 ;
      end;
      Table.Rows[Row].Data:= Cache;
      Result:= true;
    end;
  end else
  begin
    OnErrorOccur(Table, 'DumpCacheRow', 'Row (' + IntToStr(Row) + ') does not exist');
  end;
end;

procedure DumpCacheTable(var Table: TMySSQL_Table);
var
  i: integer;
begin
  for i:= 0 to GetArrayLength(Table.Rows) - 1 do
  begin
    DumpCacheRow(Table, i);
  end;
end;

procedure UncacheRow(var Table: TMySSQL_Table; const Row: word);
begin
  if (DumpCacheRow(Table, Row)) then
  begin
    Table.Rows[Row].Columns:= [];
    Table.Rows[Row].Cached:= false;
  end;
end;

function SaveTable(var Table: TMySSQL_Table): boolean;
var
  Rows: string;
  i: integer;
begin
  if (Table.Linked) then
  begin
    Rows:= '';
    DumpCacheTable(Table);
    for i:= 0 to GetArrayLength(Table.Rows) - 1 do
    begin
      Rows:= Rows + #13#10 + Table.Rows[i].Data;
    end;
    Delete(Rows, 1, 2);
    WriteFile(MySSQL_PATH + Table.Name + '.myssql', Rows);
    Table.Modified:= false;
    Result:= true;
  end else
  begin
    OnErrorOccur(Table, 'SaveTable', 'Table is not linked');
  end;
end;

function LoadTable(var Table: TMySSQL_Table; const Tablename: string): boolean;
begin
  if (FileExists(MySSQL_PATH + Tablename + '.myssql')) then
  begin
    Table.Rows:= ExplodeRow(ReadFromFile(MySSQL_PATH, Tablename + '.myssql'), #13#10);
    Table.Name:= Tablename;
    Table.Linked:= true;
    Table.Modified:= false;
    Table.Exception:= '';
    Result:= true;
  end else
  begin
    OnErrorOccur(Table, 'LoadTable', 'Unable to load table "' + Tablename + '"');
  end;
end;

function UnloadTable(var Table: TMySSQL_Table): boolean;
begin
  if (Table.Linked) then
  begin
    if (Table.Modified) then
    begin
      SaveTable(Table);
    end;
    Table.Rows:= [];
    Table.Linked:= false;
    Table.Name:= '';
    Table.Exception:= '';
    Result:= true;
  end else
  begin
    OnErrorOccur(Table, 'UnloadTable', 'Table is not linked');
  end;
end;

function CreateTable(var Table: TMySSQL_Table; const Tablename: string): boolean;
begin
  WriteFile(MySSQL_PATH + Tablename + '.myssql', '');
  if (LoadTable(Table, Tablename)) then
  begin
    Result:= true;
  end else
  begin
    OnErrorOccur(Table, 'CreateTable', 'Unable to create table "' + Tablename + '"');
  end;
end;

procedure DestroyTable(var Table: TMySSQL_Table);
begin
  Table.Rows:= [];
end;

function CreateRow(var Table: TMySSQL_Table; const Columns: array of variant): word;
begin
  Result:= GetArrayLength(Table.Rows);
  SetArrayLength(Table.Rows, Result + 1);
  Table.Rows[Result].Data:= Implode(AryVnttoAryStr(Columns), #9);
  Table.Modified:= true;
end;

function DestroyRow(var Table: TMySSQL_Table; const Row: word): boolean;
var
  HIndex: integer;
begin
  if (RowExists(Table, Row)) then
  begin
    HIndex:= GetArrayLength(Table.Rows) - 1;
    if (HIndex <> Row) then
    begin
      Table.Rows[Row]:= Table.Rows[HIndex];
    end;
    SetArrayLength(Table.Rows, iif(HIndex > 0, HIndex, 0));
    Table.Modified:= true;
    Result:= true;
  end else
  begin
    OnErrorOccur(Table, 'DestroyRow', 'Row (' + IntToStr(Row) + ') does not exist');
  end;
end;

function FetchColumn(var Table: TMySSQL_Table; const Row, Column: word; var DummyC: TMySSQL_Column): boolean;
var
  Source: string;
  TPos: integer;
  x, c: word;
begin
  if (not(RowExists(Table, Row))) then
  begin
    exit;
  end;
  Source:= Table.Rows[Row].Data;
  c:= 1;
  for x:= 0 to Column do
  begin
    Delete(Source, 1, TPos);
    c:= c + TPos;
    TPos:= Pos(#9, Source);
  end;
  if (TPos > 0) then
  begin
    DummyC.Value:= Copy(Source, 1, Pos(#9, Source) - 1);
    DummyC.Offset:= c;
    Result:= true;
  end;
end;

function FetchRowByColumn(var Table: TMySSQL_Table; const Column: word; const Value: variant): integer;
var
  C: TMySSQL_Column;
  i: integer;
begin
  Result:= -1;
  for i:= 0 to GetArrayLength(Table.Rows) - 1 do
  begin
    if ((FetchColumn(Table, i, Column, C) = true) and (C.Value = Value)) then
    begin
      Result:= i;
      break;
    end;
  end;
end;

function CacheRow(var Table: TMySSQL_Table; const Row: word): boolean;
var
  C: TMySSQL_Column;
  i, x: word;
begin
  if (RowExists(Table, Row)) then
  begin
    if (not(Table.Rows[Row].Cached)) then
    begin
      while (FetchColumn(Table, Row, i, C) = true) do
      begin
        SetArrayLength(Table.Rows[Row].Columns, x + 1);
        Table.Rows[Row].Columns[x]:= C;
        x:= x + 1;
        i:= i + 1;
      end;
      Table.Rows[Row].Cached:= true;
      Result:= true;
    end;
  end else
  begin
    OnErrorOccur(Table, 'CacheRow', 'Row (' + IntToStr(Row) + ') does not exist');
  end;
end;

function SetColumn(var Table: TMySSQL_Table; const Row, Column: word; const Value: variant): boolean;
var
  C: TMySSQL_Column;
  Cached: boolean;
begin
  if (RowExists(Table, Row)) then
  begin
    Cached:= Table.Rows[Row].Cached;
    UnCacheRow(Table, Row);
    if (FetchColumn(Table, Row, Column, C)) then
    begin
      Delete(Table.Rows[Row].Data, C.Offset, Length(C.Value));
      Insert(GetTypeOF(Value), Table.Rows[Row].Data, C.Offset);
      Table.Modified:= true;
      Result:= true;
    end else
    begin
      OnErrorOccur(Table, 'SetColumn', 'Out of row range (' + IntToStr(Row) + '/' + IntToStr(Column) + ')');
    end;
    if (Cached) then
    begin
      CacheRow(Table, Row);
    end;
  end else
  begin
    OnErrorOccur(Table, 'SetColumn', 'Row (' + IntToStr(Row) + ') does not exist');
  end;
end;

function CFetchColumn(var Table: TMySSQL_Table; const Row, Column: word): string;
begin
  if (ColumnCached(Table, Row, Column)) then
  begin
    Result:= Table.Rows[Row].Columns[Column].Value;
  end else
  begin
    OnErrorOccur(Table, 'CFetchColumn', 'Column (' + IntToStr(Column) + ') is not cached');
  end;
end;

function CSetColumn(var Table: TMySSQL_Table; const Row, Column: word; const Value: variant): boolean;
begin
  if (ColumnCached(Table, Row, Column)) then
  begin
    Table.Rows[Row].Columns[Column].Value:= GetTypeOF(Value);
    Table.Modified:= true;
    Result:= true;
  end else
  begin
    OnErrorOccur(Table, 'CSetColumn', 'Column (' + IntToStr(Column) + ') is not cached');
  end;
end;

function CIncColumn(var Table: TMySSQL_Table; const Row, Column: word; const Increase: extended): boolean;
var
  Value: string;
begin
  if (ColumnCached(Table, Row, Column)) then
  begin
    Value:= CFetchColumn(Table, Row, Column);
    if (RegExpMatch('^-?(\d+|\d+.?\d+)$', Value)) then
    begin
      Table.Rows[Row].Columns[Column].Value:= FloatToStr(StrToFloat(Value) + Increase);
      Table.Modified:= true;
      Result:= true;
    end else
    begin
      OnErrorOccur(Table, 'CIncColumn', 'Column (' + IntToStr(Column) + ') represents no numeric value');
    end;
  end else
  begin
    OnErrorOccur(Table, 'CIncColumn', 'Column (' + IntToStr(Column) + ') is not cached');
  end;
end;

function AppendColumn(var Table: TMySSQL_Table; const Row: word; const Value: variant): boolean;
var
  Cached: boolean;
begin
  if (RowExists(Table, Row)) then
  begin
    Cached:= Table.Rows[Row].Cached;
    UncacheRow(Table, Row);
    Table.Rows[Row].Data:= Table.Rows[Row].Data + GetTypeOF(Value) + #9;
    if (Cached) then
    begin
      CacheRow(Table, Row);
    end;
    Table.Modified:= true;
    Result:= true;
  end else
  begin
    OnErrorOccur(Table, 'AppendColumn', 'Row (' + IntToStr(Row) + ') does not exist');
  end;
end;

The socket server script....
Code: [Select]
const
  CLIST = 'b2bG9NLYF77heDtF|create|pong|delete|login|loginip|logout|changepass|ping';

type
  TRef = record
    Row: word;
    Name: string;
  end;

  TServer = record
    IP: string;
    ID: integer;
    Timeout: cardinal;
    Refs: array of TRef;
  end;
 
var
  Accounts, ReqQueue: TMySSQL_Table;
  Servers : array of TServer;
  Counter: integer;

function Explode(Source: string; const Delimiter: string): array of string;
var
  x, y, l: integer;
  b: word;
begin
  l:= length(Delimiter);
  b:= length(Source);
  Source:= Source + Delimiter;
  Result:= [];
  repeat
    x:= Pos(Delimiter, Source);
    SetArrayLength(Result, y + 1);
    Result[y]:= Copy(Source, 1, x - 1);
    y:= y + 1;
    Delete(Source, 1, x + l - 1);
  until (x = 0);
  SetArrayLength(Result, iif(b = 0, y - 2,  y - 1));
end;

procedure ActivateServer();
begin
  if (not(LoadTable(Accounts, 'accounts'))) then
  begin
    WriteLn('Unable to load accounts database');
    Sleep(3000);
    Shutdown;
  end;
end;

function GetNewID(): integer;
begin
  Inc(Counter, 1);
  Result:= Counter;
end;

function ServerRegistered(const SID: word): integer;
var
  i: integer;
begin
  Result:= -1;
  for i:= 0 to GetArrayLength(Servers) - 1 do
  begin
    if (Servers[i].ID = SID) then
    begin
      Result:= i;
      break;
    end;
  end;
end;

function RegisterServer(const IP: string): word;
var
  Index, i: integer;
begin
  Index:= GetArrayLength(Servers);
  for i:= 0 to Index - 1 do
  begin
    if (Servers[i].ID = 0) then
    begin
      Index:= i;
      break;
    end;
  end;
  if (Index = GetArrayLength(Servers)) then
  begin
    SetArrayLength(Servers, Index + 1);
  end;
  Servers[Index].IP:= IP;
  Servers[Index].ID:= GetNewID();
  Result:= Servers[Index].ID;
end;

procedure OnServerDisconnect(const Index: integer);
var
  i, HIndex: integer;
begin
  for i:= 0 to GetArrayLength(Servers[Index].Refs) - 1 do
  begin
    CSetColumn(Accounts, Servers[Index].Refs[i].Row, 3, 0);
    CSetColumn(Accounts, Servers[Index].Refs[i].Row, 4, false);
    UnCacheRow(Accounts, Servers[Index].Refs[i].Row);
  end;
  HIndex:= GetArrayLength(Servers) - 1;
  if (HIndex <> Index) then
  begin
    Servers[Index]:= Servers[HIndex];
  end;
  SetArrayLength(Servers, iif(HIndex > 0, HIndex, 0));
  SaveTable(Accounts);
  WriteLn('Server disconnected');
end;

function OnServerSignIn(const IP: string): string;
begin
  Result:= IntToStr(RegisterServer(IP));
end;

procedure AddReference(const Index: integer; const Row: word; const Name: string);
var
  HIndex: integer;
begin
  HIndex:= GetArrayLength(Servers[Index].Refs);
  SetArrayLength(Servers[Index].Refs, HIndex + 1);

  Servers[Index].Refs[HIndex].Row:= Row;
  Servers[Index].Refs[HIndex].Name:= Name;
  CacheRow(Accounts, Row);
end;

function GetReference(const Index: integer; const Name: string): integer;
var
  i: integer;
begin
  Result:= -1;
  for i:= 0 to GetArrayLength(Servers[Index].Refs) - 1 do
  begin
    if (Servers[Index].Refs[i].Name = Name) then
    begin
      Result:= i;
      break;
    end;
  end;
end;

function GetReferenceRow(const Index, Row: integer): integer;
var
  i: integer;
begin
  Result:= -1;
  for i:= 0 to GetArrayLength(Servers[Index].Refs) - 1 do
  begin
    if (Servers[Index].Refs[i].Row = Row) then
    begin
      Result:= i;
      break;
    end;
  end;
end;

function DeleteReference(const Index: integer; const Name: string): integer;
var
  FIndex, HIndex: integer;
begin
  FIndex:= GetReference(Index, Name);
  Result:= -1;
  if (FIndex >= 0) then
  begin
    HIndex:= GetArrayLength(Servers[Index].Refs) - 1;
    Result:= Servers[Index].Refs[FIndex].Row;
    if (HIndex <> FIndex) then
    begin
      Servers[Index].Refs[FIndex]:= Servers[Index].Refs[HIndex];
    end;
    SetArrayLength(Servers[Index].Refs, iif(HIndex > 0, HIndex, 0));
  end;
end;

function OnAccountCreate(const Index: integer; const Name, Pass, IP: string): string;
begin
  if (FetchRowByColumn(Accounts, 0, Name) < 0) then
  begin
    AddReference(Index, CreateRow(Accounts, [Name, Pass, IP, Servers[Index].ID, true]), Name);
    Result:= 'Account ' + Name + ' successfully created.';
  end else
  begin
    Result:= 'An account with the name ' + Name + ' already exists.';
  end;
end;

function OnAccountDelete(const Index: integer; const Name: string): string;
var
  Row, SID, SignedIn: integer;
begin
  Row:= DeleteReference(Index, Name);
  if (Row >= 0) then
  begin
    DestroyRow(Accounts, Row);
    // Update row reference if row deleted is referenced; intricate :/
    if (Row < GetArrayLength(Accounts.Rows)) then
    begin
      if (Accounts.Rows[Row].Cached) then
      begin
        SID:= StrToInt(CFetchColumn(Accounts, Row, 3));
        if (SID > 0) then
        begin
          SignedIn:= ServerRegistered(SID);
          if (SignedIn > -1) then
          begin
            Servers[SignedIn].Refs[GetReferenceRow(SignedIn, GetArrayLength(Accounts.Rows))].Row:= Row;
            WriteLn('Successfully updated row index after delete');
          end;
        end;
      end;
    end;
    Result:= 'Account successfully deleted.';
  end else
  begin
    Result:= 'You either have no account, or you are not logged in.';
  end;
end;

function OnPlayerLogin(const Index: integer; const Name, Password, IP: string): string;
var
  Row: integer;
  MCached: boolean;
begin
  Row:= FetchRowByColumn(Accounts, 0, Name);
  if (Row > -1) then
  begin
    if (CacheRow(Accounts, Row)) then
    begin
      MCached:= true;
    end;
    if (CFetchColumn(Accounts, Row, 1) = Password) then
    begin
      if (CFetchColumn(Accounts, Row, 4) = 'false') then
      begin
        CSetColumn(Accounts, Row, 2, IP);
        CSetColumn(Accounts, Row, 3, Servers[Index].ID);
        CSetColumn(Accounts, Row, 4, true);
        AddReference(Index, Row, Name);
        MCached:= false;
        Result:= 'Successfully logged in.';
      end else
      begin
        Result:= 'Account already in use.';
      end;
    end else
    begin
      Result:= 'Invalid password.';
    end;
  end else
  begin
    Result:= 'Account does not exist.';
  end;
  if (MCached) then
  begin
    UnCacheRow(Accounts, Row);
  end;
end;


function OnPlayerChangePass(const Index: integer; const Name, NewPass: string): string;
var
  Row: integer;
begin
  Row:= GetReference(Index, Name);
  if (Row >= 0) then
  begin
    if (CSetColumn(Accounts, Row, 1, NewPass)) then
    begin
      Result:= 'Password changed';
    end else
    begin
      Result:= 'An unexpected database error occured. Try again later.';
    end;
  end else
  begin
    Result:= 'You either do not have an account, or you are not logged in.';
  end;   
end;

function OnPlayerLogout(const Index: integer; const Name: string): string;
var
  Row: integer;
begin
  Row:= DeleteReference(Index, Name);
  if (Row >= 0) then
  begin
    CSetColumn(Accounts, Row, 3, 0);
    CSetColumn(Accounts, Row, 4, false);
    UncacheRow(Accounts, Row);
    Result:= 'Successfully logged out.';
  end else
  begin
    Result:= 'You are not logged in, or you do not have an account.';
  end;
end;

function OnPlayerLoginIP(const Index: integer; const Name, IP: string): string;
var
  Row: integer;
  MCached: boolean;
begin
  Row:= FetchRowByColumn(Accounts, 0, Name);
  if (Row > -1) then
  begin
    if (CacheRow(Accounts, Row)) then
    begin
      MCached:= true;
    end;
    if (CFetchColumn(Accounts, Row, 2) = IP) then
    begin
      if (CFetchColumn(Accounts, Row, 4) = 'false') then
      begin
        CSetColumn(Accounts, Row, 3, Servers[Index].ID);
        CSetColumn(Accounts, Row, 4, true);
        AddReference(Index, Row, Name);
        MCached:= false;
        Result:= 'Successfully logged in';
      end else
      begin
        Result:= 'Account already in use.';
      end;
    end else
    begin
      Result:= 'IP does not match.';
    end;
  end else
  begin
    Result:= 'Account does not exist.';
  end;
  if (MCached) then
  begin
    UnCacheRow(Accounts, Row);
  end;
end;

procedure SendResponse(const RID: word; const Reply: string);
begin
  TCPAdminPM(CFetchColumn(ReqQueue, RID, 0), CFetchColumn(ReqQueue, RID, 1) + #9 + CFetchColumn(ReqQueue, RID, 2) + #9 + Reply);
end;

procedure Pong(const Index: integer);
begin
  Servers[Index].Timeout:= 0;
end;

procedure Ping(const IP: string; const SID: integer);
begin
  TCPAdminPM(IP, IntToStr(SID) + #9 + '-1' + #9 + 'ping');
end;

procedure OnAdminDisconnect(IP: string);
var
  i: integer;
begin
  for i:= 0 to GetArrayLength(Servers) - 1 do
  begin
    if ((Servers[i].IP = IP) and (Servers[i].ID > 0)) then
    begin
      Ping(IP, Servers[i].ID);
      Servers[i].Timeout:= GetTickCount() + 180;
    end;
  end;
end;

procedure OnAdminMessage(const IP, Package: string);
begin
  if (RegExpMatch('^(-?\d+)\t(-?\d+)\t(' + CLIST + ')(\t[\w !-~€@]+)*$', Package)) then
  begin
    CreateRow(ReqQueue, [IP, Package]);
  end;
end;

procedure AppOnIdle(Ticks: integer);
var
  i, Index: integer;
  Reply: string;
begin
  if (Accounts.Exception <> '') then
  begin
    WriteLn(Accounts.Exception);
  end;
  for i:= 0 to GetArrayLength(Servers) - 1 do
  begin
    if (Servers[i].Timeout > 0) then
    begin
      if (GetTickCount() >= Servers[i].Timeout) then
      begin
        OnServerDisconnect(i);
      end;
    end;
  end;
  for i:= 0 to GetArrayLength(ReqQueue.Rows) - 1 do
  begin
    CacheRow(ReqQueue, i);
    Reply:= '';
    Index:= ServerRegistered(StrToInt(CFetchColumn(ReqQueue, i, 1)));
    if (Index > -1) then
    begin
      case CFetchColumn(ReqQueue, i, 3) of
        'create'    : Reply:= OnAccountCreate(Index, CFetchColumn(ReqQueue, i, 4), CFetchColumn(ReqQueue, i, 5), CFetchColumn(ReqQueue, i, 6));
        'delete'    : Reply:= OnAccountDelete(Index, CFetchColumn(ReqQueue, i, 4));
        'login'     : Reply:= OnPlayerLogin(Index, CFetchColumn(ReqQueue, i, 4),  CFetchColumn(ReqQueue, i, 5),  CFetchColumn(ReqQueue, i, 6));
        'loginip'   : Reply:= OnPlayerLoginIP(Index, CFetchColumn(ReqQueue, i, 4),  CFetchColumn(ReqQueue, i, 5));
        'logout'    : Reply:= OnPlayerLogout(Index, CFetchColumn(ReqQueue, i, 4));
        'changepass': Reply:= OnPlayerChangePass(Index, CFetchColumn(ReqQueue, i, 4), CFetchColumn(ReqQueue, i, 5));
        'pong'      : Pong(Index);
        'ping'      : SendData(Servers[Index].ID, 'pong' + #13#10);
        else Reply:= 'Unknown command';
      end;
    end else
    begin
      case CFetchColumn(ReqQueue, i, 3) of
        'b2bG9NLYF77heDtF': Reply:= OnServerSignIn(CFetchColumn(ReqQueue, i, 0));
      end;
    end;
    if (Reply <> '') then
    begin
      SendResponse(i, Reply);
    end;
  end;
  DestroyTable(ReqQueue);
end;
Soldat Global Account System: #soldat.sgas @ quakenet