/SSHClient/ClientSSH2.pas

https://bitbucket.org/reiniero/smalltools · Pascal · 360 lines · 270 code · 25 blank · 65 comment · 18 complexity · b0cd6691402507514d915c1e1906c10f MD5 · raw file

  1. unit ClientSSH2;
  2. { Wrapper around Synapse libraries and SSL library (libssh2+libssl
  3. is used right now)
  4. Download compiled Windows dll from e.g.
  5. http://alxdm.dyndns-at-work.com:808/files/windll_libssh2.zip
  6. Download FreePascal interface files:
  7. http://www.lazarus.freepascal.org/index.php/topic,15935.msg86465.html#msg86465
  8. This unit allows the user to send Telnet or SSH commands and get the output
  9. Thanks to Leonardo Rame
  10. http://leonardorame.blogspot.com/2010/01/synapse-based-ssh-client.html
  11. and Ludo Brands.
  12. Written by Reinier Olislagers 2011.
  13. Modified for libssh2 by Alexey Suhinin 2012.
  14. Modified: no exceptions but error messages in connect 2013
  15. Modified: slight renaming of enums 2013
  16. License of code:
  17. * MIT
  18. * LGPLv2 or later (with FreePascal static linking exception)
  19. * GPLv2 or later
  20. according to your choice.
  21. Free use allowed but please don't sue or blame me.
  22. Uses other libraries/components; different licenses may apply that also can influence the combined/compiled work.
  23. }
  24. {$mode objfpc}{$H+}
  25. {$DEFINE HAS_SSH_SUPPORT} //comment out if only telnet support required
  26. {$DEFINE LIBSSH2} //Specify encryption library to use
  27. interface
  28. uses
  29. Classes, SysUtils,
  30. tlntsend,
  31. {$IFDEF HAS_SSH_SUPPORT}
  32. {ssl - or actually ssh - libs required by tlntsend}
  33. {$IFDEF LIBSSH2}
  34. ssl_libssh2
  35. {$ELSE}
  36. ssl_cryptlib
  37. {$ENDIF}
  38. {$ENDIF HAS_SSH_SUPPORT} ;
  39. type
  40. TProtocolType = (ptTelnet, ptSSH); //Different means of connecting
  41. TServerType = (stUnix, stWindows); //line endings, mostly
  42. { TTelnetSSHClient }
  43. TTelnetSSHClient = class(TTelnetSend)
  44. protected
  45. FConnected: boolean;
  46. FOutputPosition: integer; //Keeps track of position in output stream
  47. FProtocolType: TProtocolType;
  48. FServerLineEnding: string; //depends on FServerType
  49. FServerType: TServerType;
  50. FWelcomeMessage, FTelnetLoginPrompt, FTelnetPasswordPrompt: string;
  51. procedure SetPrivateKeyFile(Value: string);
  52. function GetPrivateKeyFile: string;
  53. { Based on protocol and servertype, set expected serverside line ending}
  54. procedure DetermineLineEnding;
  55. { Sets port if no explicit port set. Uses protocol type: SSH or telnet}
  56. procedure DeterminePort;
  57. function GetSessionLog: string;
  58. procedure ProtocolTypeChange(Value: TProtocolType);
  59. function ReceiveData: string; //Can be used to get welcome message etc.
  60. procedure SendData(Data: string);
  61. procedure ServerTypeChange(Value: TServerType);
  62. public
  63. {All output generated during the entire session up to now}
  64. property AllOutput: string read GetSessionLog;
  65. {True if connected to server}
  66. property Connected: boolean read FConnected;
  67. {Name or IP address of host to connect to}
  68. property HostName: string read FTargetHost write FTargetHost;
  69. {Port on host used for connection. If left as 0, it will be determined by protocol type (22 for SSH, 23 for Telnet}
  70. property Port: String read FTargetPort write FTargetPort;
  71. {Location of private key file.}
  72. property PrivateKeyFile: string read GetPrivateKeyFile write SetPrivateKeyFile;
  73. {Telnet login prompt, default 'login:'}
  74. property TelnetLoginPrompt: string read FTelnetLoginPrompt write FTelnetLoginPrompt;
  75. {Telnet password prompt, default 'password:'}
  76. property TelnetPasswordPrompt: string read FTelnetPasswordPrompt write FTelnetPasswordPrompt;
  77. {Username used when connecting}
  78. property UserName: string read FUserName write FUserName;
  79. {Password used when connecting. Used as passphrase if PrivateKey is used}
  80. property Password: string read FPassword write FPassword;
  81. {Should we talk Telnet or SSH to the server? Defaults to SSH.}
  82. property ProtocolType: TProtocolType read FProtocolType write ProtocolTypeChange;
  83. {Windows or Unix/Linux server? Has effect on line endings. Defaults to Unix. NOTE: untested}
  84. property Servertype: TServerType read FServerType write ServerTypeChange;
  85. {Initial message displayed on logon}
  86. property WelcomeMessage: string read FWelcomeMessage;
  87. {Connect/logon to server. Requires that all authentication, protocol and hostname/port options are correct
  88. Returns descriptive result. You can then use the Connected property.}
  89. function Connect: string;
  90. {If connected, logoff from server}
  91. procedure Disconnect;
  92. {Send command to server; do not wait for response}
  93. procedure Command(CommandText: string);
  94. {Send command to server and receive resulting output}
  95. function CommandResult(CommandText: string): string;
  96. constructor Create;
  97. destructor Destroy; override;
  98. end;
  99. implementation
  100. { TelnetSSHClient }
  101. procedure TTelnetSSHClient.SetPrivateKeyFile(Value: string);
  102. begin
  103. Sock.SSL.PrivateKeyFile := value;
  104. end;
  105. function TTelnetSSHClient.GetPrivateKeyFile: string;
  106. begin
  107. Result := Sock.SSL.PrivateKeyFile;
  108. end;
  109. procedure TTelnetSSHClient.DetermineLineEnding;
  110. begin
  111. case FProtocolType of
  112. ptSSH:
  113. begin
  114. if FServerType = stUnix then
  115. FServerLineEnding := #10 //Unix
  116. else
  117. FServerLineEnding := #13 + #10; //windows
  118. end;
  119. ptTelnet:
  120. begin
  121. if FServerType = stUnix then
  122. FServerLineEnding := #10 //Unix
  123. else
  124. FServerLineEnding := #13 + #10; //windows
  125. end;
  126. else
  127. raise Exception.Create('Unknown protocol type');
  128. end;
  129. end;
  130. procedure TTelnetSSHClient.DeterminePort;
  131. begin
  132. if FTargetPort = '' then
  133. //Set default port for protocol
  134. begin
  135. case FProtocolType of
  136. ptTelnet: FTargetPort := '23';
  137. ptSSH: FTargetPort := '22';
  138. else
  139. raise Exception.Create('Unknown protocol type.');
  140. end;
  141. end;
  142. end;
  143. procedure TTelnetSSHClient.ServerTypeChange(Value: TServerType);
  144. begin
  145. FServerType := Value;
  146. DetermineLineEnding;
  147. end;
  148. function TTelnetSSHClient.Connect: string;
  149. var
  150. Received: string;
  151. begin
  152. result:='Unknown error while connecting';
  153. FOutputPosition := 1; //First character in output stream
  154. FWelcomeMessage := '';
  155. //Just to make sure:
  156. DetermineLineEnding;
  157. DeterminePort;
  158. if FTargetPort='0' then
  159. begin
  160. result:='Port may not be 0.';
  161. exit; //jump out of function
  162. end;
  163. case FProtocolType of
  164. ptTelnet:
  165. begin
  166. try
  167. if Login then
  168. begin
  169. FConnected := True;
  170. result:='Connected to telnet server.';
  171. end
  172. else
  173. if Sock.LastError<>0 then raise Exception.Create(Sock.LastErrorDesc);
  174. except
  175. on E: Exception do
  176. begin
  177. FConnected:=false;
  178. result:='Error connecting to telnet server '+FTargetHost+':'+
  179. FTargetPort+' as user ' + FUserName +
  180. '. Technical details: '+E.Message;
  181. end;
  182. end;
  183. end;
  184. ptSSH:
  185. begin
  186. {$IFNDEF HAS_SSH_SUPPORT}
  187. raise Exception.Create(
  188. 'SSH support has not been compiled into the telnetsshclient library.');
  189. {$ENDIF HAS_SSH_SUPPORT}
  190. try
  191. // Repurpose password property as passphrase if keyfile is used
  192. if (PrivateKeyFile <> '') and (FPassword <> '') then
  193. Sock.SSL.KeyPassword:=FPassword;
  194. if SSHLogin then
  195. begin
  196. FConnected := True;
  197. result:='Connected to SSH server.';
  198. end
  199. else
  200. begin
  201. if Sock.LastError<>0 then
  202. begin
  203. FConnected:=false;
  204. result:='Error connecting to SSH server '+FTargetHost+':'+
  205. FTargetPort+' as user ' + FUserName +
  206. '. Technical details: socket error: '+Sock.LastErrorDesc;
  207. end;
  208. if Sock.SSL.LastError<0 then
  209. begin
  210. FConnected:=false;
  211. result:='Error connecting to SSH server '+FTargetHost+':'+
  212. FTargetPort+' as user ' + FUserName +
  213. '. Technical details: socket encryption error: '+Sock.SSL.LastErrorDesc;
  214. end;
  215. end;
  216. except
  217. on E: Exception do
  218. begin
  219. FConnected:=false;
  220. result:='Error connecting to SSH server '+FTargetHost+':'+
  221. FTargetPort+' as user ' + FUserName +
  222. '. Technical details: '+E.Message;
  223. end;
  224. end;
  225. end;
  226. else
  227. begin
  228. FConnected:=false;
  229. result:='Error connecting to SSH server '+FTargetHost+':'+
  230. FTargetPort+' as user ' + FUserName +
  231. '. Technical details: unknown protocol type.';
  232. end;
  233. end;
  234. if FConnected = True then
  235. begin
  236. Received := ReceiveData;
  237. FWelcomeMessage:=Received; //Copy over for archival
  238. if FProtocolType=ptTelnet then
  239. begin
  240. //Unfortunately, we'll have to extract login ourselves
  241. //Hope it applies to all server types.
  242. if (AnsiPos(AnsiLowerCase(FTelnetLoginPrompt),AnsiLowerCase(Received))>0) then
  243. begin
  244. SendData(UserName);
  245. Received:=ReceiveData;
  246. end;
  247. if (AnsiPos(AnsiLowerCase(FTelnetPasswordPrompt),AnsiLowerCase(Received))>0) then
  248. begin
  249. SendData(Password);
  250. end;
  251. //Receive additional welcome message/message of the day
  252. FWelcomeMessage:=FWelcomeMessage+LineEnding+ReceiveData;
  253. end;
  254. end;
  255. end;
  256. procedure TTelnetSSHClient.Disconnect;
  257. begin
  258. Logout;
  259. FConnected := False;
  260. end;
  261. procedure TTelnetSSHClient.Command(CommandText: string);
  262. begin
  263. if FConnected then
  264. begin
  265. SendData(CommandText);
  266. end
  267. else
  268. begin
  269. raise Exception.Create('Can only run command when connected');
  270. end;
  271. end;
  272. function TTelnetSSHClient.ReceiveData: string;
  273. begin
  274. Result := '';
  275. while Sock.CanRead(1000) or (Sock.WaitingData > 0) do
  276. begin
  277. Sock.RecvPacket(1000);
  278. Result := Result + Copy(SessionLog, FOutputPosition,
  279. Length(SessionLog));
  280. FOutputPosition := Length(SessionLog) + 1;
  281. end;
  282. end;
  283. procedure TTelnetSSHClient.SendData(Data: string);
  284. begin
  285. Data := Data + FServerLineEnding; //Could be linux, could be Windows
  286. Send(Data);
  287. end;
  288. function TTelnetSSHClient.GetSessionLog: string;
  289. begin
  290. // Gets complete output up to now
  291. Result := SessionLog;
  292. end;
  293. procedure TTelnetSSHClient.ProtocolTypeChange(Value: TProtocolType);
  294. begin
  295. FProtocolType := Value;
  296. //Auto-determine port and line ending, if necessary
  297. DeterminePort;
  298. DetermineLineEnding;
  299. end;
  300. function TTelnetSSHClient.CommandResult(CommandText: string): string;
  301. begin
  302. Result := '';
  303. if FConnected then
  304. begin
  305. SendData(CommandText);
  306. Result := ReceiveData; //gets too much
  307. end
  308. else
  309. begin
  310. Result := '';
  311. raise Exception.Create('Can only run command when connected');
  312. end;
  313. end;
  314. constructor TTelnetSSHClient.Create;
  315. begin
  316. inherited;
  317. FConnected := False;
  318. FProtocolType := ptSSH; //Could be telnet, too
  319. FServerType := stUnix; //Probably a safe default.
  320. FTelnetLoginPrompt := 'login:';
  321. FTelnetPasswordPrompt := 'password:';
  322. DetermineLineEnding;
  323. DeterminePort;
  324. end;
  325. destructor TTelnetSSHClient.Destroy;
  326. begin
  327. if FConnected then
  328. Disconnect;
  329. inherited Destroy;
  330. end;
  331. end.