program sshtest;
// https://www.libssh2.org/examples/ssh2_exec.html
{$mode objfpc}{$H+}

uses {$IFDEF UNIX}
  cthreads, {$ENDIF}
  SysUtils,
  Classes,
  blcksock,
  libssh2;

type

  { TMyThread }

  TReadThread = class(TThread)
  private
    FChannel: PLIBSSH2_CHANNEL;
  protected
    procedure Execute; override;
  public
    constructor Create(channel: PLIBSSH2_CHANNEL);
  end;



var
  sock: TTCPBlockSocket;
  session: PLIBSSH2_SESSION;
  fingerprint: PAnsiChar;
  password, s, ssend: string;
  channel: PLIBSSH2_CHANNEL;
  i: integer;
  ReadThread: TReadThread;
  bNewString: boolean;

  { TReadThread }

  procedure TReadThread.Execute;
  var
    buf: array[0..10000] of char;
    len: integer;
  begin
    libssh2_channel_set_blocking(channel, 0);
    while not Terminated do
    begin
      len := libssh2_channel_read(channel, buf, 10000);
      if len > 0 then
      begin
        Write(copy(buf, 1, len));
      end
      else if bNewString then
      begin
        libssh2_channel_write(channel, PChar(ssend), length(ssend));
        bNewString := False;
      end
      else
        sleep(100);
    end;
  end;

  constructor TReadThread.Create(channel: PLIBSSH2_CHANNEL);
  begin
    inherited Create(True);
    FChannel := channel;
  end;



  // Ejemplo de scp
  // https://github.com/libssh2/libssh2/blob/master/example/scp_write.c

  procedure scp_write(session: PLIBSSH2_SESSION; archi, scppath: PAnsiChar);
  var
    channel: PLIBSSH2_CHANNEL;
    f: TFileStream;
    errmsg: PAnsiChar;
    errlen: integer;
    err: integer;
    nread: integer;
    mem: array[0 .. (1024 - 1)] of AnsiChar;
    ptr: PChar;
    rc: integer;

  begin
    f := TFileStream.Create(archi, fmOpenRead);


    //* Send a file via scp. The mode parameter must only have permissions!
    channel := libssh2_scp_send(session, scppath, &0440,
      // fileinfo.st_mode & 0777
      f.size);


    if (channel = nil) then
    begin
      err := libssh2_session_last_error(session, errmsg, errlen, 0);
      writeln('Unable to open a session: ' + IntToStr(err) + ' ' + errmsg);
      f.Free;
      exit;
    end;

    writeln('SCP session waiting to send file');
    while True do
    begin
      nread := f.Read(mem, sizeof(mem));
      if (nread <= 0) then
      begin
        // end of file
        break;
      end;
      ptr := @mem[0];

      repeat
        // write the same data over and over, until error or completion
        rc := libssh2_channel_write(channel, ptr, nread);
        if (rc < 0) then
        begin
          writeln('ERROR ' + IntToStr(rc));
          break;
        end
        else
        begin
          // rc indicates how many bytes were written this time
          ptr := ptr + rc;
          nread := nread - rc;
        end
      until nread <= 0;
    end;

    f.Free;

    writeln('Sending EOF');
    libssh2_channel_send_eof(channel);

    writeln('Waiting for EOF');
    libssh2_channel_wait_eof(channel);

    writeln('Waiting for channel to close');
    libssh2_channel_wait_closed(channel);

  end;


  procedure scp_read(session: PLIBSSH2_SESSION; scppath, archi: PAnsiChar);

  var
    channel: PLIBSSH2_CHANNEL;
    f: TFileStream;
    mem: array[0 .. (1024 - 1)] of AnsiChar;
    rc: integer;
    fileinfo: struct_stat;
    got: integer;
    amount: integer;
    k: integer;

  label
    lbl_fin;

  begin
    f := TFileStream.Create(archi, fmCreate);

    for k:= 1 to 100 do
        fileinfo.barrera[k]:= 255;

    // Request a file via SCP
    channel := libssh2_scp_recv(session, scppath, fileinfo);

    if (channel = nil) then
    begin
      writeln('Unable to open a session: ' + IntToStr(
        libssh2_session_last_errno(session)));
      exit;
    end;



    got := 0;
    while (got < fileinfo.st_size) do
    begin
      amount := sizeof(mem);

      if ((fileinfo.st_size - got) < amount) then
      begin
        amount := (fileinfo.st_size - got);
      end;

      rc := libssh2_channel_read(channel, mem, amount);
      if (rc > 0) then
      begin
        f.Write(mem, rc);
      end
      else if (rc < 0) then
      begin
        writeln('libssh2_channel_read() failed: ' + IntToStr(rc));
        break;
      end;
      got := got + rc;
    end;
(*
   amount := sizeof(mem);
   rc := libssh2_channel_read(channel, mem, amount);
    if (rc < 0) then
       begin
         writeln('libssh2_channel_read() failed: ' + IntToStr(rc));
         goto lbl_fin;
       end;

    while (rc >0) do
    begin
      if mem[rc-1] = #0 then
      begin
        f.Write(mem, rc-1);
        rc:= -1;
      end
      else
      begin
        f.Write(mem, rc);
        rc := libssh2_channel_read(channel, mem, amount);
        if (rc < 0) then
        begin
          writeln('libssh2_channel_read() failed: ' + IntToStr(rc));
          break;
        end;
      end;
      got := got + rc;
    end;
  *)

  lbl_fin:
    f.Free;

    libssh2_channel_free(channel);
    channel := nil;
  end;

procedure CrearArchiTest( archi: string );

var
  f: TFileStream;
  buff: packed array[0..10] of byte;
  k: integer;

begin
  f:= TFileStream.Create( archi, fmCreate );
  for k:= 0 to 9 do
    buff[k]:= k;
  buff[10]:= 0;
  f.Write( buff, sizeof( buff ) );
  f.Free;
end;

begin

{$IFDEF WINDOWS}
(*
WSADATA wsadata;
    int err;

    err = WSAStartup(MAKEWORD(2, 0), &wsadata);
    if(err != 0) then
    begin
        fprintf(stderr, "WSAStartup failed with error: %d\n", err);
        return 1;
     end;
     *)
     {$ENDIF}


  if Paramcount < 2 then
  begin
    writeln('Usage: ', ParamStr(0), ' user ip');
    exit;
  end;
  sock := TTCPBlockSocket.Create;
  sock.Connect(ParamStr(2), '22');
  if sock.LastError = 0 then
  begin
    session := libssh2_session_init();
    if libssh2_session_startup(session, sock.Socket) <> 0 then
    begin
      writeln('Cannot establishing SSH session');
      exit;
    end;
    fingerprint := libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);
    Write('Host fingerprint ');
    i := 0;
    while fingerprint[i] <> #0 do
    begin
      Write(inttohex(Ord(fingerprint[i]), 2), ':');
      i := i + 1;
    end;
    writeln;
    writeln('Assuming known host...');


    Write('Password for ', ParamStr(1), ' : ');
    readln(password);
    if libssh2_userauth_password(session, PChar(ParamStr(1)), PChar(password)) <> 0 then
    begin
      writeln('Authentication by password failed');
      exit;
    end;

(***
/* Or by public key */
#define HOME "/home/username/"
if(libssh2_userauth_publickey_fromfile(session, username,
                                       HOME ".ssh/id_rsa.pub",
                                       HOME ".ssh/id_rsa",
                                       password)) {
    fprintf(stderr, "\tAuthentication by public key failed\n");
    goto shutdown;
}

***)


    writeln('Authentication succeeded');




    channel := libssh2_channel_open_session(session);
    if not assigned(channel) then
    begin
      writeln('Cannot open session');
      exit;
    end;
    if libssh2_channel_request_pty(channel, 'vanilla') <> 0 then
    begin
      writeln('Cannot obtain pty');
      exit;
    end;
    if libssh2_channel_shell(channel) <> 0 then
    begin
      writeln('Cannot open shell');
      exit;
    end;


    (*
    // CONSOLA
    ReadThread := TReadThread.Create(channel);
    ReadThread.Resume;
    while True do
    begin
      readln(s);
      if s = 'exit' then
        break;
      ssend := s + LineEnding;
      bNewString := True;
    end;
    ReadThread.Terminate;

   *)

  //  CrearArchiTest( '/home/rchaer/basura/test.bin' );

  //  scp_write(session, '/home/rchaer/basura/test.bin', 'basura/test.bin');
    scp_read(session, 'basura/test.bin', '/home/rchaer/basura/x.bin');

    libssh2_channel_free(channel);
    libssh2_session_disconnect(session, 'Thank you for using sshtest');
    libssh2_session_free(session);
    sock.Free;
  end
  else
    writeln('Cannot connect');

end.

