0

I'm trying to write an app that will programmatically log in to a remote device using SSH much like an expect script (I know I can use expect but I would like to do this in Obj-c).

I have researched a lot on this and know that I need to use a pty. The code I have works fine for telnet but I can't seem to get ssh to work. It seems as though SSH is not using the pty to ask for the password. When I execute the following code I see the device asking for the password, but I don't see my NSLog output.

I'm very new to this and probably over my head, but I'd really appreciate anyone who can help me get this working.

#import <Foundation/Foundation.h>
#import <util.h>

@interface NSTask (PTY)

- (NSFileHandle *)masterSideOfPTYOrError:(NSError **)error;

@end

@implementation NSTask (PTY)

- (NSFileHandle *)masterSideOfPTYOrError:(NSError *__autoreleasing *)error {
int fdMaster, fdSlave;
int rc = openpty(&fdMaster, &fdSlave, NULL, NULL, NULL);
if (rc != 0) {
    if (error) {
        *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil];
    }
    return NULL;
}
fcntl(fdMaster, F_SETFD, FD_CLOEXEC);
fcntl(fdSlave, F_SETFD, FD_CLOEXEC);
NSFileHandle *masterHandle = [[NSFileHandle alloc] initWithFileDescriptor:fdMaster closeOnDealloc:YES];
NSFileHandle *slaveHandle = [[NSFileHandle alloc] initWithFileDescriptor:fdSlave closeOnDealloc:YES];
self.standardInput = slaveHandle;
self.standardOutput = slaveHandle;
return masterHandle;
}

@end

int main(int argc, const char * argv[])
{

@autoreleasepool {

    NSTask *task = [[NSTask alloc] init];

    [task setLaunchPath:@"/usr/bin/ssh"];

    [task setArguments:@[@"user@192.168.1.1"]];

    NSError *error;
    NSFileHandle *masterHandle = [task masterSideOfPTYOrError:&error];
    if (!masterHandle) {
        NSLog(@"error: could not set up PTY for task: %@", error);
        exit(0);
    }

    [task launch];

    [masterHandle waitForDataInBackgroundAndNotify];
    NSMutableString *buff = [[NSMutableString alloc] init];
    [[NSNotificationCenter defaultCenter] addObserverForName:NSFileHandleDataAvailableNotification
                                                      object:masterHandle queue:nil
                                                  usingBlock:^(NSNotification *note)
     {
         NSData *outData = [masterHandle availableData];
         NSString *outStr = [[NSString alloc] initWithData:outData encoding:NSUTF8StringEncoding];
         [buff appendString:outStr];
         NSLog(@"output: %@", outStr);

         NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"sername:"
                                                                                options:NSRegularExpressionCaseInsensitive
                                                                                  error:nil];
         NSTextCheckingResult *match = [regex firstMatchInString:buff
                                                         options:0
                                                           range:NSMakeRange(0, [buff length])];
         if (match) {
             NSLog(@"got a match!!");
             [buff setString:@""];
             [masterHandle writeData:[@"bhughes\n" dataUsingEncoding:NSUTF8StringEncoding]];
         }

         NSLog(@"Exiting function.\n");
         [masterHandle waitForDataInBackgroundAndNotify];
     }];

    [task waitUntilExit];

    NSLog(@"Program complete.\n");
}
return 0;
}
SAT
  • 647
  • 1
  • 12
  • 23

1 Answers1

1

As far as I know, NSTask does not support pty ability. Working with something like ssh requires interactive pty device context.

The simplest way to do this is forkpty, but this forks process itself, so it cannot be used with NSTask.

Finally I wrote a wrapper class that manages forkpty. That forks a child process and calls forkpty and execve.

Here's my implementation: https://github.com/eonil/PseudoTeletypewriter.Swift

You can read/write using single master device file handle. I confirmed that sudo is working, and I believe ssh also should work fine.

eonil
  • 83,476
  • 81
  • 317
  • 516
  • Is it possible you update it for Swift 3.0 as it is supposed to be stable and future releases will be backwards compatible? – Arqu Sep 27 '16 at 20:04