Last active
July 22, 2020 20:14
-
-
Save rafiki270/c004b92deca437934f702efd3508bd83 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//: [Previous](@previous) | |
import Foundation | |
URLCache.shared = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil) | |
public struct FailerMessage { | |
var to: [String] = [] | |
var cc: [String]? = [] | |
var bcc: [String]? = [] | |
var from: String | |
var subject: String | |
var body: String? | |
var attachments: [Data]? = [] | |
} | |
public enum FailerError: Error { | |
case internalFailerError | |
case unableToConnectToHost | |
} | |
public final class Failer: NSObject { | |
public typealias Login = (username: String, password: String) | |
open var server: String | |
open var port: Int | |
open var login: Login? | |
fileprivate var inputStream: InputStream! | |
fileprivate var outputStream: OutputStream! | |
fileprivate let maxReadLength = 1024 | |
// MARK: Initialization | |
public init(server: String, port: Int = 25, login: Login? = nil) { | |
self.server = server | |
self.port = port | |
self.login = login | |
} | |
// MARK: Sending | |
public func send(_ message: FailerMessage) throws { | |
try connect() | |
let emailData: Data = try compile(message: message) | |
try send(emailData) | |
} | |
// MARK: Connection | |
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() | |
} | |
private func compile(message: FailerMessage) throws -> Data { | |
let headersString = headers(from: message) | |
guard var emailData = headersString.data(using: String.Encoding.utf8) else { | |
throw FailerError.internalFailerErrorq | |
} | |
if let attachment = try attachments(from: message) { | |
emailData.append(attachment) | |
} | |
return emailData | |
} | |
private func send(_ data: Data) throws { | |
print(data) | |
_ = data.withUnsafeBytes { | |
outputStream.write($0, maxLength: data.count) | |
} | |
} | |
fileprivate func finish() { | |
inputStream.close() | |
outputStream.close() | |
} | |
} | |
fileprivate extension Failer { | |
func headers(from message: FailerMessage) -> String { | |
var content = "From: \(message.from)\r\n" | |
content += "To: \(message.to.joined(separator: ", "))\r\n" | |
if let cc = message.cc, cc.count > 0 { | |
content += "Cc: \(cc.joined(separator: ", "))\r\n" | |
} | |
if let bcc = message.bcc, bcc.count > 0 { | |
content += "Bcc: \(bcc.joined(separator: ", "))\r\n" | |
} | |
content += "Subject: \(message.subject)\r\n" | |
// TODO: Handle email body | |
content += "\r\n" | |
return content | |
} | |
func attachments(from message: FailerMessage) throws -> Data? { | |
return nil | |
} | |
} | |
extension Failer: StreamDelegate { | |
public func stream(_ aStream: Stream, handle eventCode: Stream.Event) | |
{ | |
switch eventCode | |
{ | |
case Stream.Event.endEncountered: | |
print("socked died") | |
aStream.close() | |
aStream.remove(from: RunLoop.main, forMode: .defaultRunLoopMode) | |
break | |
case Stream.Event.hasSpaceAvailable: | |
print("matching presented certificate with expected") | |
var sslInfo: SecTrust? = aStream.property(forKey: kCFStreamPropertySSLContext as Stream.PropertyKey) as! SecTrust? | |
sslInfo = aStream.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) as! SecTrust? | |
sslInfo = aStream.property(forKey: kCFStreamSSLValidatesCertificateChain as Stream.PropertyKey) as! SecTrust? | |
sslInfo = aStream.property(forKey: kCFStreamPropertySSLSettings as Stream.PropertyKey) as! SecTrust? | |
sslInfo = aStream.property(forKey: kCFStreamSSLLevel as Stream.PropertyKey) as! SecTrust? | |
sslInfo = aStream.property(forKey: kCFStreamSSLPeerName as Stream.PropertyKey) as! SecTrust? | |
print(sslInfo ?? "notnin'") | |
// Get the presented certificate | |
let sslTrustInput: SecTrust? = aStream.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) as! SecTrust? | |
if (sslTrustInput == nil) { | |
print("something went horribly wrong in fetching the presented certificate") | |
broadcastSocketResult(result: false) | |
return | |
} | |
// if (Vars.expectedCert == nil) { | |
// print("probably a bug, there is no expected certificate in Vars. fail/crash in 3, 2, 1...") | |
// broadcastSocketResult(result: false) | |
// return | |
// } | |
//set the expected certificate as the only "trusted" one | |
// let acceptedCerts: NSMutableArray = NSMutableArray() | |
// acceptedCerts.add(Vars.expectedCert!) | |
// SecTrustSetAnchorCertificates(sslTrustInput!, acceptedCerts) | |
//check the certificate match test results | |
var result: SecTrustResultType = SecTrustResultType.fatalTrustFailure //must initialize with something | |
let err: OSStatus = SecTrustEvaluate(sslTrustInput!, &result) | |
if (err != errSecSuccess) { | |
print("problem evaluating certificate match") | |
broadcastSocketResult(result: false) | |
return | |
} | |
if (result != SecTrustResultType.proceed) { | |
print("certificate was not signed by private CA") | |
broadcastSocketResult(result: false) | |
return | |
} | |
print("socket ssl turned out ok") | |
broadcastSocketResult(result: true) | |
break | |
case Stream.Event.openCompleted: | |
print("Socket is useable") | |
break | |
case Stream.Event.errorOccurred: | |
print("Error: \(aStream.streamError)") | |
broadcastSocketResult(result: false) | |
break; | |
default: | |
print("Some other code" + String(describing: eventCode)) | |
broadcastSocketResult(result: false) | |
break | |
} | |
} | |
private func broadcastSocketResult(result: Bool) { | |
//let extras = [Const.BORADCAST_SOCKET_RESULT: result] | |
//NotificationCenter.default.post(name: NSNotification.Name(rawValue: Const.BROADCAST_SOCKET), object: extras) | |
} | |
private func readAvailableBytes(stream: InputStream) { | |
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: maxReadLength) | |
while stream.hasBytesAvailable { | |
let numberOfBytesRead = inputStream.read(buffer, maxLength: maxReadLength) | |
if numberOfBytesRead < 0 { | |
if let _ = inputStream.streamError { | |
break | |
} | |
} | |
if let output = processedMessageString(buffer: buffer, length: numberOfBytesRead) { | |
print(output) | |
} | |
} | |
} | |
private func processedMessageString(buffer: UnsafeMutablePointer<UInt8>, length: Int) -> String? { | |
let string = String(bytesNoCopy: buffer, length: length, encoding: .ascii, freeWhenDone: true) | |
return string | |
} | |
} | |
let failer = Failer(server: "smtp.gmail.com", port: 465, login: nil) | |
let message = FailerMessage(to: ["[email protected]"], cc: nil, bcc: nil, from: "[email protected]", subject: "Test subject Yo!", body: "Ondrej has a beautiful body", attachments: nil) | |
do { | |
try failer.send(message) | |
} | |
catch { | |
print("Failer error: \(error)") | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment