2

I need to connect to an SMTP server and failing open the connection. Failing handshake (missing authentication) is furthest I got here. I open the socket to a normal server but failing to do so in here to send an email.

    private func connect() throws {
        var input: InputStream? = nil
        var output: OutputStream? = nil

        Stream.getStreamsToHost(withName: server, port: port, inputStream: &input, outputStream: &output)

        guard let inputSafe = input, let outputSafe = output else {
            throw FailerError.unableToConnectToHost
        }

        self.inputStream = inputSafe
        self.outputStream = outputSafe

        // TODO: Authentication using login

        // Enable SSL/TLS on the streams
//        inputStream!.setProperty(kCFStreamSocketSecurityLevelNegotiatedSSL, forKey: Stream.PropertyKey(rawValue: kCFStreamPropertySocketSecurityLevel as String))
//        outputStream!.setProperty(kCFStreamSocketSecurityLevelNegotiatedSSL, forKey: Stream.PropertyKey(rawValue: kCFStreamPropertySocketSecurityLevel as String))
//        
//        // Define custom SSL/TLS settings
//        let sslSettings: [NSString : Any] = [
//             NSStream automatically sets up the socket, the streams and creates a trust object and evaulates it before you even get a chance to check the trust yourself. Only proper SSL certificates will work with this method. If you have a self signed certificate like I do, you need to disable the trust check here and evaulate the trust against your custom root CA yourself.
//            NSString(format: kCFStreamSSLValidatesCertificateChain): kCFBooleanFalse,
//            //
//            NSString(format: kCFStreamSSLPeerName): kCFNull,
//            // We are an SSL/TLS client, not a server
//            NSString(format: kCFStreamSSLIsServer): kCFBooleanFalse,
//            
//            NSString(format: kCFStreamSocketSecurityLevelNegotiatedSSL): kCFBooleanTrue
//        ]
//        
//        // Set the SSL/TLS settingson the streams
//        inputStream!.setProperty(sslSettings, forKey: Stream.PropertyKey(rawValue: kCFStreamPropertySSLSettings as String))
//        outputStream!.setProperty(sslSettings, forKey: Stream.PropertyKey(rawValue: kCFStreamPropertySSLSettings as String))


        inputStream.delegate = self
        outputStream.delegate = self

        inputStream.schedule(in: .main, forMode: .commonModes)
        outputStream.schedule(in: .main, forMode: .commonModes)

        inputStream.open()
        outputStream.open()
    }

The commented part shows me trying to set some of the stuff but don't really know what is supposed to be there. Any ideas anyone please?

Just to clarify, this is for a badly designed API that is using SMTP to receive data (not my idea but have to use it) so please don't suggest MessageUI or that the app will get rejected because of sending emails directly :)

Full gist of the Playground is here if anyone is interested: https://gist.github.com/rafiki270/c004b92deca437934f702efd3508bd83

Ondrej Rafaj
  • 4,070
  • 7
  • 38
  • 61

1 Answers1

0

@Ondrej As per Apple documentation "Introduction to Stream Programming Guide for Cocoa" it is mentioned that the NSStream ( Objective C ) / Stream (Swift) class does not support connecting to a remote host on iOS. Your alternate is to use CFStream by taking advantage of the toll-free bridge between CFStream and NSStream to cast your CFStreams to NSStreams.

Below is an example on how you do it with CFStream.

CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)[website host], 80, &readStream, &writeStream);

NSInputStream *inputStream = (__bridge_transfer NSInputStream *)readStream;
NSOutputStream *outputStream = (__bridge_transfer NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];

Hope this helps.

Shreekara
  • 139
  • 2
  • 8
  • this is great but would you know how to do the authentication? – Ondrej Rafaj Aug 07 '17 at 09:00
  • 1
    Plus still don't get why would you have `Stream.getStreamsToHost(withName: server, port: port...` available in the API if the connections to remote hosts are disabled? – Ondrej Rafaj Aug 07 '17 at 09:01
  • Are you planning to use a client certificate for authentication ? – Shreekara Aug 07 '17 at 17:36
  • It needs to connect directly to an SMTP server, authenticate and send an email ... dude if you know how to do that I'll love you forever! :D – Ondrej Rafaj Aug 07 '17 at 21:06
  • @Ondrej Well I wanted to help you out but I have limited time. You may please check this project https://github.com/jetseven/skpsmtpmessage which pretty much does what you want. – Shreekara Aug 08 '17 at 04:11