Skip to content

Instantly share code, notes, and snippets.

@rafiki270
Last active July 22, 2020 20:14
Show Gist options
  • Save rafiki270/c004b92deca437934f702efd3508bd83 to your computer and use it in GitHub Desktop.
Save rafiki270/c004b92deca437934f702efd3508bd83 to your computer and use it in GitHub Desktop.
//: [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