serverthread.pas

来自「著名的SecureBlackBox控件完整源码」· PAS 代码 · 共 1,275 行 · 第 1/3 页

PAS
1,275
字号
      hu := copy(p, 1, i - 1);
      delete(p, 1, i);
    end
    else
    begin
      hu := p;
      p := '';
    end;
    if hu = '.' then
      continue;
    if hu = '..' then
    begin
      us := UpDir(us);
      continue;
    end;
    us := us + '/' + hu;
    i := pos('/', p);
  end;
  result := us + p;
  if result = '' then
    result := '/';
  if d <> '' then
    result := d + result;
end;

function FormRealPath(us, curr: widestring): widestring;
begin
  us := curr + us;
  result := ResolveDots(us);
end;

function DrivePresent(const DrivePath: string): Boolean;
var
  i: UINT;
begin
  if (Length(DrivePath) = 0) or (not (DrivePath[1] in ['A'..'Z', 'a'..'z'])) then
  begin
    result := false;
    exit;
  end;
  i := GetDriveType(PChar(DrivePath));
  Result := (i <> DRIVE_NO_ROOT_DIR) and (i <> DRIVE_UNKNOWN);
end;

function DirectoryExists(DirName: string): boolean;
var
  SRec: TSearchRec;
begin
  Result := false;

  if Length(DirName) = 3 then
  begin
    result := DrivePresent(DirName);
    exit;
  end
  else
    if (Length(DirName) > 3) and (AnsiChar(DirName[Length(DirName)]) in ['\',
      '/']) then
    Delete(DirName, Length(DirName), 1);

  if FindFirst(DirName, faAnyFile, SRec) = 0 then
  begin
    if (SRec.Attr and faDirectory) > 0 then
      Result := true;
  end;
  SysUtils.FindClose(SRec);
end;

////////////////////////////////////////////////////////////////////////////////
// TSSHServerThread class

constructor TSSHServerThread.Create(ASocket: TServerClientWinSocket);
begin
  inherited Create(true, ASocket);
  FSSHServer := TElSSHServer.Create(nil);
  FHostKeys := TElSSHMemoryKeyStorage.Create(nil);
  Synchronize(SetupHostKeys);
  SetupServer;
  FSubsystemThreads := TList.Create;
  FServerForwardings := TList.Create;
  Log('Server instance is ready');
  FCurrentDir := '/';
end;

destructor TSSHServerThread.Destroy;
var
  I: integer;
begin
  for I := 0 to FSubsystemThreads.Count - 1 do
    TThread(FSubsystemThreads[I]).Terminate;
  FreeAndNil(FSubsystemThreads);
  for I := 0 to FServerForwardings.Count - 1 do
  begin
    TServerSocket(FServerForwardings[I]).Close;
    TServerSocket(FServerForwardings[I]).Free;
  end;
  if FSSHServer.Active then
    FSSHServer.Close(true);
  FreeAndNil(FServerForwardings);
  FreeAndNil(FSSHServer);
  FreeAndNil(FHostKeys);
  inherited;
end;

procedure TSSHServerThread.Execute;
begin
  ChangeState(ssConnecting);
  FSSHServer.Open;
  inherited;
end;

procedure TSSHServerThread.ClientExecute;
var
  FDSet: TFDSet;
  TimeVal: TTimeVal;
begin
  ChangeState(ssSecuring);
  while not Terminated and ClientSocket.Connected do
  begin
    try
      FD_ZERO(FDSet);
      FD_SET(ClientSocket.SocketHandle, FDSet);
      TimeVal.tv_sec := 0;
      TimeVal.tv_usec := 500000;
      if (select(0, @FDSet, nil, nil, @TimeVal) > 0) and not Terminated then
        FSSHServer.DataAvailable
      else
        Sleep(500);
    except
      break;
    end;
  end;
  try
    if FSSHServer.Active then
      FSSHServer.Close(not ClientSocket.Connected);
    if ClientSocket.Connected then
      ClientSocket.Close;
  except
  end;
  ChangeState(ssDisconnected);
  Synchronize(DoConnClosed);
end;

procedure TSSHServerThread.SetupHostKeys;
var
  Key: TElSSHKey;
begin
  if Length(Settings.HostKey) > 0 then
  begin
    Key := TElSSHKey.Create;
    try
      if Key.LoadPrivateKey(@Settings.HostKey[1], Length(Settings.HostKey)) = 0 then
        FHostKeys.Add(Key);
    finally
      Key.Free;
    end;
  end;
end;

procedure TSSHServerThread.SetupServer;
begin
  FSSHServer.SoftwareName := 'SSHBlackbox(VCL)';
  FSSHServer.OnAuthAttempt := OnSrvAuthAttempt;
  FSSHServer.OnAuthFailed := OnSrvAuthFailed;
  FSSHServer.OnAuthPassword := OnSrvAuthPassword;
  FSSHServer.OnAuthPublicKey := OnSrvAuthPublicKey;
  FSSHServer.OnAuthKeyboard := OnSrvAuthKeyboard;
  FSSHServer.OnAuthKeyboardResponse := OnSrvAuthKeyboardResponse;
  FSSHServer.OnFurtherAuthNeeded := OnSrvFurtherAuthNeeded;
  FSSHServer.OnSend := OnSrvSend;
  FSSHServer.OnReceive := OnSrvReceive;
  FSSHServer.OnOpenConnection := OnSrvOpen;
  FSSHServer.OnCloseConnection := OnSrvClose;
  FSSHServer.OnError := OnSrvError;
  FSSHServer.OnOpenSubsystem := OnSrvOpenSubsystem;
  FSSHServer.KeyStorage := FHostKeys;
  FSSHServer.AllowedSubsystems.Add('sftp');
end;

procedure TSSHServerThread.OnSrvAuthAttempt(Sender: TObject; const Username: string;
  AuthType: integer; var Accept: boolean);
begin
  Log('User ' + Username + ' requests ' + AuthTypeToStr(AuthType) + ' authentication');
  ChangeState(ssAuthenticating);
  if FAuthAllUsername <> UserName then
  begin
    FAuthAllTypes := 0;
    FSSHServer.AuthenticationTypes := SSH_AUTH_TYPE_PASSWORD or SSH_AUTH_TYPE_PUBLICKEY or
      SSH_AUTH_TYPE_KEYBOARD;
    FAuthAllUsername := UserName;
  end;
  if Assigned(FOnAuthAttempt) then
  begin
    FCurrUsername := Username;
    FCurrAuthType := AuthType;
    FUsername := Username;
    Synchronize(DoAuthAttempt);
    Accept := FCurrBool;
  end
  else
    Accept := false;
end;

procedure TSSHServerThread.OnSrvAuthFailed(Sender: TObject; AuthenticationType: integer);
begin
  Log('Authentication attempt (' + AuthTypeToStr(AuthenticationType) + ') failed');
end;

procedure TSSHServerThread.OnSrvAuthPublicKey(Sender: TObject; const Username: string;
  Key: TElSSHKey; var Accept: boolean);
begin
  Log('User ' + Username + ' tries PublicKey authentication');
  FCurrKey := Key;
  if Assigned(FOnAuthPublicKey) then
    Synchronize(DoAuthPublicKey);
  Accept := FCurrBool;
  if Accept then
    Log('User ' + Username + ' is allowed to try PublicKey authentication');
end;

procedure TSSHServerThread.OnSrvAuthPassword(Sender: TObject; const Username: string;
  const Password: string; var Accept: boolean; var ForceChangePassword: boolean);
begin
  Log('User ' + Username + ' tries Password authentication');
  if Assigned(FOnAuthPassword) then
  begin
    FCurrUsername := Username;
    FCurrPassword := Password;
    Synchronize(DoAuthPassword);
    Accept := FCurrBool;
  end
  else
    Accept := false;
  ForceChangePassword := false;
  if Accept then
    Log('Password authentication succeeded for user ' + Username);
end;

procedure TSSHServerThread.OnSrvAuthKeyboard(Sender: TObject; const Username: string;
  Submethods: TStringList; var Name: string; var Instruction: string;
  Requests: TStringList; Echoes: TBits);
begin
  Log('User ' + Username + ' tries Keyboard-interactive authentication');
  Name := '';
  Instruction := 'Please enter your username and password';
  Requests.Clear;
  Requests.Add('Username: ');
  Requests.Add('Password: ');
  Echoes.Size := 2;
  Echoes[0] := true;
  Echoes[1] := false;
end;

procedure TSSHServerThread.OnSrvAuthKeyboardResponse(Sender: TObject; Requests: TStringList;
  Responses: TStringList; var Name: string; var Instruction: string;
  NewRequests: TStringList; Echoes: TBits; var Accept: boolean);
begin
  if (Assigned(FOnAuthKeyboard)) and (Responses.Count = 2) then
  begin
    FCurrUsername := Responses[0];
    FCurrPassword := Responses[1];
    Synchronize(DoAuthKeyboard);
    Accept := FCurrBool;
  end
  else
    Accept := false;
  if Accept then
    Log('Keyboard-interactive authentication succeeded for user ' + Username);
end;

procedure TSSHServerThread.OnSrvSend(Sender: TObject; Buffer: pointer; Size: longint);
var
  Sent: integer;
  Ptr: ^byte;
begin
  Ptr := Buffer;
  if ClientSocket <> nil then
    while (Size > 0) and ClientSocket.Connected do
    begin
      Sent := ClientSocket.SendBuf(Ptr^, Size);
      Inc(Ptr, Sent);
      Dec(Size, Sent);
    end;
end;

procedure TSSHServerThread.OnSrvReceive(Sender: TObject; Buffer: pointer; MaxSize: longint; out Written: longint);
begin
  Written := ClientSocket.ReceiveBuf(Buffer^, MaxSize);
  if Written < 0 then
    Written := 0;
  if Written = 0 then
    ClientSocket.Close;
end;

procedure TSSHServerThread.OnSrvOpen(Sender: TObject);
begin
  Log('SSH connection established');
  ChangeState(ssActive);
end;

procedure TSSHServerThread.OnSrvClose(Sender: TObject);
begin
  if not Terminated then
  begin
    Log('SSH connection closed');
    ChangeState(ssDisconnected);
  end;
  if (ClientSocket <> nil) and ClientSocket.Connected then
    ClientSocket.Close;
end;

procedure TSSHServerThread.OnSrvError(Sender: TObject; ErrorCode: integer);
begin
  Log('Error ' + IntToStr(ErrorCode));
end;

procedure TSSHServerThread.OnSrvOpenSubsystem(Sender: TObject; Conn: TElSSHTunnelConnection;
  const Subsystem: string);
var
  Thread: TElSSHSubsystemThread;
begin
  if (lowercase(Subsystem) = 'sftp') then
  begin
    Log('Subsystem (' + Subsystem + ') opened');
    Thread := TElSSHSubsystemThread.Create(TElSFTPSSHSubsystemHandler, Conn, true);
    TElSFTPSSHSubsystemHandler(Thread.Handler).Server.OnOpen := OnSftpOpen;
    TElSFTPSSHSubsystemHandler(Thread.Handler).Server.OnCloseHandle := OnSftpCloseHandle;
    TElSFTPSSHSubsystemHandler(Thread.Handler).Server.OnCreateDirectory := OnSftpCreateDirectory;
    TElSFTPSSHSubsystemHandler(Thread.Handler).Server.OnFindClose := OnSftpFindClose;
    TElSFTPSSHSubsystemHandler(Thread.Handler).Server.OnFindFirst := OnSftpFindFirst;
    TElSFTPSSHSubsystemHandler(Thread.Handler).Server.OnFindNext := OnSftpFindNext;
    TElSFTPSSHSubsystemHandler(Thread.Handler).Server.OnOpenFile := OnSftpOpenFile;
    TElSFTPSSHSubsystemHandler(Thread.Handler).Server.OnReadFile := OnSftpReadFile;
    TElSFTPSSHSubsystemHandler(Thread.Handler).Server.OnRemove := OnSftpRemove;
    TElSFTPSSHSubsystemHandler(Thread.Handler).Server.OnRenameFile := OnSftpRenameFile;
    TElSFTPSSHSubsystemHandler(Thread.Handler).Server.OnRequestAbsolutePath := OnSftpRequestAbsolutePath;
    TElSFTPSSHSubsystemHandler(Thread.Handler).Server.OnRequestAttributes := OnSftpRequestAttributes;
    TElSFTPSSHSubsystemHandler(Thread.Handler).Server.OnRequestAttributes2 := OnSftpRequestAttributes2;
    TElSFTPSSHSubsystemHandler(Thread.Handler).Server.OnWriteFile := OnSftpWriteFile;
    TElSFTPSSHSubsystemHandler(Thread.Handler).Server.Versions := [sbSFTP0, sbSFTP1, sbSFTP2, sbSFTP3, sbSFTP4, sbSFTP5, sbSFTP6];
    FSubsystemThreads.Add(Thread);
    Thread.Resume;
    ChangeState(ssActive);
  end
  else
    Log('Custom subsystem requested: ' + Subsystem);
end;

procedure TSSHServerThread.DoLog;
begin
  FOnLog(Self, FCurrLogLine);
end;

procedure TSSHServerThread.DoAuthAttempt;
begin
  FCurrBool := false;
  FOnAuthAttempt(Self, FCurrUsername, FCurrAuthType, FCurrBool);
end;

procedure TSSHServerThread.DoAuthPassword;
var
  FCP: boolean;
begin
  FCurrBool := false;
  FOnAuthPassword(Self, FCurrUsername, FCurrPassword, FCurrBool, FCP);
end;

procedure TSSHServerThread.DoConnClosed;
begin
  if Assigned(FOnConnClosed) then
    FOnConnClosed(Self);
end;

procedure TSSHServerThread.DoRefreshInfo;
begin
  if Assigned(FOnRefreshInfo) then
    FOnRefreshInfo(Self);
end;

procedure TSSHServerThread.Log(const S: string);
begin
  if Assigned(FOnLog) then
  begin
    FCurrLogLine := S;
    Synchronize(DoLog);
  end;
end;

function TSSHServerThread.GetTunnelCount: integer;
var
  I: integer;
begin
  Result := FSubsystemThreads.Count;
  for I := 0 to FServerForwardings.Count - 1 do
    Result := Result + TServerSocket(FServerForwardings[I]).Socket.ActiveConnections;
end;

procedure TSSHServerThread.ChangeState(NewState: TSSHServerState);
begin
  FState := NewState;
  Synchronize(DoRefreshInfo);
end;

function TSSHServerThread.FileInfoFromRec(r: WIN32_FIND_DATA): TElSftpFileInfo;
var
  inf: TElSftpFileInfo;
begin
  inf := TElSftpFileInfo.Create;
  if SftpVersion > 3 then
    inf.Name := ConvertToUTF8String(string(r.cFileName))
  else
  begin
    inf.Name := string(r.cFileName);
    inf.LongName := UnixListing(r);
  end;
  inf.Attributes.Size := Int64(r.nFileSizeHigh shl 32) + r.nFileSizeLow;
  if (r.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) <> 0 then
  begin
    inf.Attributes.Directory := true;
    inf.Attributes.FileType := ftDirectory;
  end
  else
  begin
    inf.Attributes.Directory := false;
    inf.Attributes.FileType := ftFile;

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?