Search code examples
iosswiftsmtpsmtp-auth

Open socket connection to SMTP server using Stream in Swift 3


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


Solution

  • @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.