ObjSSH.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. /*
  2. Copyright (c) 2011 Christoffer Lejdborg
  3. Permission is hereby granted, free of charge, to any person
  4. obtaining a copy of this software and associated documentation
  5. files (the "Software"), to deal in the Software without
  6. restriction, including without limitation the rights to use,
  7. copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. copies of the Software, and to permit persons to whom the
  9. Software is furnished to do so, subject to the following
  10. conditions:
  11. The above copyright notice and this permission notice shall be
  12. included in all copies or substantial portions of the Software.
  13. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  14. EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  15. OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  16. NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  17. HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  18. WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  20. OTHER DEALINGS IN THE SOFTWARE.
  21. -------------------------------------------------------------------------------
  22. The project contains modified code from the examples at
  23. http://libssh2.org/examples/ssh2_exec.html
  24. */
  25. #import "ObjSSH.h"
  26. #import "libssh2.h"
  27. #include <sys/socket.h>
  28. #include <arpa/inet.h>
  29. @implementation ObjSSH
  30. unsigned long hostaddr;
  31. int sock;
  32. struct sockaddr_in soin;
  33. int rc;
  34. LIBSSH2_SESSION *session;
  35. LIBSSH2_CHANNEL *channel;
  36. static int waitsocket(int socket_fd, LIBSSH2_SESSION *session) {
  37. struct timeval timeout;
  38. int rc;
  39. fd_set fd;
  40. fd_set *writefd = NULL;
  41. fd_set *readfd = NULL;
  42. int dir;
  43. timeout.tv_sec = 10;
  44. timeout.tv_usec = 0;
  45. FD_ZERO(&fd);
  46. FD_SET(socket_fd, &fd);
  47. /* now make sure we wait in the correct direction */
  48. dir = libssh2_session_block_directions(session);
  49. if(dir & LIBSSH2_SESSION_BLOCK_INBOUND)
  50. readfd = &fd;
  51. if(dir & LIBSSH2_SESSION_BLOCK_OUTBOUND)
  52. writefd = &fd;
  53. rc = select(socket_fd + 1, readfd, writefd, NULL, &timeout);
  54. return rc;
  55. }
  56. // -----------------------------------------------------------------------------
  57. // INITIALIZATION
  58. // -----------------------------------------------------------------------------
  59. + (id)connectToHost:(NSString *)host withUsername:(NSString *)username password:(NSString *)password error:(NSError **)error {
  60. if (host == nil)
  61. return nil;
  62. ObjSSH *ssh = [[ObjSSH alloc] initWithHost:host username:username password:password publicKey:nil privateKey:nil];
  63. return [ssh connect:error] ? ssh : nil;
  64. }
  65. + (id)connectToHost:(NSString *)host withUsername:(NSString *)username publicKey:(NSString *)publicKey privateKey:(NSString *)privateKey error:(NSError **)error {
  66. if (host == nil)
  67. return nil;
  68. ObjSSH *ssh = [[ObjSSH alloc] initWithHost:host username:username password:nil publicKey:publicKey privateKey:privateKey];
  69. return [ssh connect:error] ? ssh : nil;
  70. }
  71. - (id)initWithHost:(NSString *)host username:(NSString *)username password:(NSString *)password publicKey:(NSString *)publicKey privateKey:(NSString *)priateKey {
  72. self = [super init];
  73. if (self) {
  74. // Set defaults from parameters
  75. _host = host;
  76. _port = [NSNumber numberWithInt:22];
  77. _username = username;
  78. _password = password;
  79. _publicKey = publicKey;
  80. _privateKey = priateKey;
  81. // Find out the ip address and port number from host
  82. NSMutableArray *hostParts = (NSMutableArray *)[host componentsSeparatedByString:@":"];
  83. if ([hostParts count] > 1) {
  84. NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
  85. [formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]];
  86. NSNumber *port = [formatter numberFromString:[hostParts objectAtIndex:1]];
  87. if (port) {
  88. _port = port;
  89. _host = [hostParts objectAtIndex:0];
  90. }
  91. }
  92. }
  93. return self;
  94. }
  95. // -----------------------------------------------------------------------------
  96. // HANDLE CONNECTIONS
  97. // -----------------------------------------------------------------------------
  98. - (BOOL)connect:(NSError **)error {
  99. hostaddr = inet_addr([_host cStringUsingEncoding:NSUTF8StringEncoding]);
  100. sock = socket(AF_INET, SOCK_STREAM, 0);
  101. soin.sin_family = AF_INET;
  102. soin.sin_port = htons([_port intValue]);
  103. soin.sin_addr.s_addr = hostaddr;
  104. // Connect to socket
  105. if (connect(sock, (struct sockaddr*)(&soin),sizeof(struct sockaddr_in)) != 0) {
  106. NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary];
  107. [errorDetail setValue:@"Failed to connect" forKey:NSLocalizedDescriptionKey];
  108. *error = [NSError errorWithDomain:@"ObjSSH" code:100 userInfo:errorDetail];
  109. return NO;
  110. }
  111. // Create a session instance
  112. session = libssh2_session_init();
  113. if (!session) {
  114. NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary];
  115. [errorDetail setValue:@"Failed to create a session instance" forKey:NSLocalizedDescriptionKey];
  116. *error = [NSError errorWithDomain:@"ObjSSH" code:101 userInfo:errorDetail];
  117. return NO;
  118. }
  119. // Tell libssh2 we want it all done non-blocking
  120. libssh2_session_set_blocking(session, 0);
  121. // Start it up. This will trade welcome banners, exchange keys,
  122. // and setup crypto, compression, and MAC layers
  123. while ((rc = libssh2_session_startup(session, sock)) == LIBSSH2_ERROR_EAGAIN);
  124. if (rc) {
  125. NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary];
  126. [errorDetail setValue:[NSString stringWithFormat:@"Failed establishing SSH session: %d", rc] forKey:NSLocalizedDescriptionKey];
  127. *error = [NSError errorWithDomain:@"ObjSSH" code:102 userInfo:errorDetail];
  128. return NO;
  129. }
  130. const char *username = [_username cStringUsingEncoding:NSUTF8StringEncoding];
  131. const char *password = [_password cStringUsingEncoding:NSUTF8StringEncoding];
  132. if ([_privateKey length] == 0) {
  133. // We could authenticate via password
  134. while ((rc = libssh2_userauth_password(session, username, password)) == LIBSSH2_ERROR_EAGAIN);
  135. if (rc) {
  136. NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary];
  137. [errorDetail setValue:@"Authentication by password failed" forKey:NSLocalizedDescriptionKey];
  138. *error = [NSError errorWithDomain:@"ObjSSH" code:103 userInfo:errorDetail];
  139. return NO;
  140. }
  141. }
  142. else {
  143. // Or by public key
  144. while ((rc = libssh2_userauth_publickey_fromfile(session, username, [_publicKey cStringUsingEncoding:NSUTF8StringEncoding], [_privateKey cStringUsingEncoding:NSUTF8StringEncoding], password)) == LIBSSH2_ERROR_EAGAIN);
  145. if (rc) {
  146. NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary];
  147. [errorDetail setValue:@"Authentication by public key failed" forKey:NSLocalizedDescriptionKey];
  148. *error = [NSError errorWithDomain:@"ObjSSH" code:100 userInfo:errorDetail];
  149. return NO;
  150. }
  151. }
  152. return YES;
  153. }
  154. - (void)disconnect {
  155. libssh2_session_disconnect(session, "Disconnect");
  156. libssh2_session_free(session);
  157. close(sock);
  158. }
  159. // -----------------------------------------------------------------------------
  160. // EXECUTION
  161. // -----------------------------------------------------------------------------
  162. - (NSString *)execute:(NSString *)command error:(NSError **)error {
  163. NSString *result = nil;
  164. // Exececute command non-blocking on the remote host
  165. while ( (channel = libssh2_channel_open_session(session)) == NULL && libssh2_session_last_error(session, NULL, NULL, 0) == LIBSSH2_ERROR_EAGAIN ) {
  166. waitsocket(sock, session);
  167. }
  168. if ( channel == NULL ) {
  169. NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary];
  170. [errorDetail setValue:@"An error occured while opening a channel on the remote host" forKey:NSLocalizedDescriptionKey];
  171. *error = [NSError errorWithDomain:@"ObjSSH" code:201 userInfo:errorDetail];
  172. return nil;
  173. }
  174. while ( (rc = libssh2_channel_exec(channel, [command cStringUsingEncoding:NSUTF8StringEncoding])) == LIBSSH2_ERROR_EAGAIN ) {
  175. waitsocket(sock, session);
  176. }
  177. if ( rc != 0 ) {
  178. NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary];
  179. [errorDetail setValue:@"An error occured while executing command on remote server" forKey:NSLocalizedDescriptionKey];
  180. *error = [NSError errorWithDomain:@"ObjSSH" code:100 userInfo:errorDetail];
  181. return nil;
  182. }
  183. for ( ;; ) {
  184. // Loop until we block
  185. int rc1;
  186. do {
  187. char buffer[0x9000];
  188. rc1 = libssh2_channel_read(channel, buffer, sizeof(buffer));
  189. if ( rc1 > 0 ) {
  190. result = [NSString stringWithCString:buffer encoding:NSASCIIStringEncoding];
  191. NSLog(@"result: %@", result);
  192. }
  193. }
  194. while ( rc1 > 0 );
  195. // This is due to blocking that would occur otherwise so we loop on
  196. // this condition
  197. if ( rc1 == LIBSSH2_ERROR_EAGAIN ) {
  198. waitsocket(sock, session);
  199. }
  200. else {
  201. break;
  202. }
  203. }
  204. while ( (rc = libssh2_channel_close(channel)) == LIBSSH2_ERROR_EAGAIN ) waitsocket(sock, session);
  205. libssh2_channel_free(channel);
  206. channel = NULL;
  207. return result;
  208. }
  209. // -----------------------------------------------------------------------------
  210. // SCP
  211. // -----------------------------------------------------------------------------
  212. - (BOOL)uploadFile:(NSString *)localPath to:(NSString *)remotePath error:(NSError **)error {
  213. struct stat fileinfo;
  214. size_t nread;
  215. char mem[1024*100];
  216. char *ptr;
  217. size_t prev;
  218. // Read local file
  219. FILE *local = fopen([localPath cStringUsingEncoding:NSUTF8StringEncoding], "rb");
  220. if (!local) {
  221. NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary];
  222. [errorDetail setValue:[NSString stringWithFormat:@"Can't read local file: %@", localPath] forKey:NSLocalizedDescriptionKey];
  223. *error = [NSError errorWithDomain:@"ObjSSH" code:401 userInfo:errorDetail];
  224. return NO;
  225. }
  226. stat([localPath cStringUsingEncoding:NSUTF8StringEncoding], &fileinfo);
  227. // Send the file via SCP
  228. do {
  229. channel = libssh2_scp_send(session, [remotePath cStringUsingEncoding:NSUTF8StringEncoding], fileinfo.st_mode & 0777, (unsigned long)fileinfo.st_size);
  230. if ((!channel) && (libssh2_session_last_errno(session) != LIBSSH2_ERROR_EAGAIN)) {
  231. char *err_msg;
  232. libssh2_session_last_error(session, &err_msg, NULL, 0);
  233. NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary];
  234. [errorDetail setValue:[NSString stringWithCString:err_msg encoding:NSUTF8StringEncoding] forKey:NSLocalizedDescriptionKey];
  235. *error = [NSError errorWithDomain:@"ObjSSH" code:402 userInfo:errorDetail];
  236. return NO;
  237. }
  238. } while (!channel);
  239. do {
  240. nread = fread(mem, 1, sizeof(mem), local);
  241. if (nread <= 0) {
  242. // end of file
  243. break;
  244. }
  245. ptr = mem;
  246. prev = 0;
  247. do {
  248. while ((rc = libssh2_channel_write(channel, ptr, nread)) == LIBSSH2_ERROR_EAGAIN) {
  249. waitsocket(sock, session);
  250. prev = 0;
  251. }
  252. if (rc < 0) {
  253. break;
  254. }
  255. else {
  256. prev = nread;
  257. // rc indicates how many bytes were written this time
  258. nread -= rc;
  259. ptr += rc;
  260. }
  261. } while (nread);
  262. } while (!nread); // only continue if nread was drained
  263. // Sending EOF
  264. while (libssh2_channel_send_eof(channel) == LIBSSH2_ERROR_EAGAIN);
  265. // Waiting for EOF
  266. while (libssh2_channel_wait_eof(channel) == LIBSSH2_ERROR_EAGAIN);
  267. // Waiting for channel to close
  268. while (libssh2_channel_wait_closed(channel) == LIBSSH2_ERROR_EAGAIN);
  269. libssh2_channel_free(channel);
  270. channel = NULL;
  271. return YES;
  272. }
  273. - (BOOL)downloadFile:(NSString *)remotePath to:(NSString *)localPath error:(NSError **)error {
  274. int spin = 0;
  275. struct stat fileinfo;
  276. off_t got = 0;
  277. int localFile = open([localPath cStringUsingEncoding:NSUTF8StringEncoding], O_WRONLY|O_CREAT, 0755);
  278. do {
  279. channel = libssh2_scp_recv(session, [remotePath cStringUsingEncoding:NSUTF8StringEncoding], &fileinfo);
  280. if (!channel) {
  281. if (libssh2_session_last_errno(session) == LIBSSH2_ERROR_EAGAIN) {
  282. waitsocket(sock, session);
  283. }
  284. else {
  285. char *err_msg;
  286. libssh2_session_last_error(session, &err_msg, NULL, 0);
  287. NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary];
  288. [errorDetail setValue:[NSString stringWithCString:err_msg encoding:NSUTF8StringEncoding] forKey:NSLocalizedDescriptionKey];
  289. *error = [NSError errorWithDomain:@"ObjSSH" code:301 userInfo:errorDetail];
  290. return NO;
  291. }
  292. }
  293. } while (!channel);
  294. // libssh2_scp_recv() is done, now receive data
  295. while (got < fileinfo.st_size) {
  296. char mem[1024*24];
  297. int rc;
  298. do {
  299. int amount = sizeof(mem);
  300. if ((fileinfo.st_size -got) < amount) {
  301. amount = fileinfo.st_size - got;
  302. }
  303. // Loop until we block
  304. rc = libssh2_channel_read(channel, mem, amount);
  305. if (rc > 0) {
  306. write(localFile, mem, rc);
  307. got += rc;
  308. }
  309. } while (rc > 0);
  310. if ((rc == LIBSSH2_ERROR_EAGAIN) && (got < fileinfo.st_size)) {
  311. // This is due to blocking that would occur otherwise
  312. // so we loop on this condition
  313. spin++;
  314. waitsocket(sock, session);
  315. continue;
  316. }
  317. break;
  318. }
  319. libssh2_channel_free(channel);
  320. channel = NULL;
  321. close(localFile);
  322. return YES;
  323. }
  324. // -----------------------------------------------------------------------------
  325. // MEMORY STUFF
  326. // -----------------------------------------------------------------------------
  327. - (void)dealloc {
  328. /*
  329. [_host release];
  330. [_port release];
  331. [_username release];
  332. [_password release];
  333. [_privateKey release];
  334. [_publicKey release];
  335. [super dealloc];
  336. */
  337. }
  338. @end