Files
MagnumOpus/Packages/MagnumOpusCore/Sources/SMTPClient/SMTPConnection.swift
T
2026-03-14 05:25:03 +01:00

91 lines
2.7 KiB
Swift

import Foundation
import NIO
@preconcurrency import NIOSSL
import Models
actor SMTPConnection {
private let host: String
private let port: Int
private let security: SMTPSecurity
private let group: EventLoopGroup
private var channel: Channel?
private let responseHandler: SMTPResponseHandler
init(host: String, port: Int, security: SMTPSecurity) {
self.host = host
self.port = port
self.security = security
self.group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
self.responseHandler = SMTPResponseHandler()
}
func connect() async throws -> SMTPResponse {
let handler = responseHandler
let hostname = host
let bootstrap: ClientBootstrap
if security == .ssl {
let sslContext = try NIOSSLContext(configuration: TLSConfiguration.makeClientConfiguration())
bootstrap = ClientBootstrap(group: group)
.channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.channelInitializer { channel in
let sslHandler = try! NIOSSLClientHandler(context: sslContext, serverHostname: hostname)
return channel.pipeline.addHandlers([sslHandler, handler])
}
} else {
// STARTTLS: start plain, upgrade later
bootstrap = ClientBootstrap(group: group)
.channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.channelInitializer { channel in
channel.pipeline.addHandler(handler)
}
}
do {
channel = try await bootstrap.connect(host: host, port: port).get()
} catch {
throw SMTPError.connectionFailed(error.localizedDescription)
}
return try await handler.waitForGreeting()
}
func sendCommand(_ command: String) async throws -> SMTPResponse {
guard let channel else { throw SMTPError.notConnected }
let handler = responseHandler
var buffer = channel.allocator.buffer(capacity: command.utf8.count + 2)
buffer.writeString(command + "\r\n")
channel.writeAndFlush(buffer, promise: nil)
return try await handler.waitForResponse()
}
func upgradeToTLS() async throws {
guard let channel else { throw SMTPError.notConnected }
let sslContext = try NIOSSLContext(configuration: TLSConfiguration.makeClientConfiguration())
let sslHandler = try NIOSSLClientHandler(context: sslContext, serverHostname: host)
do {
try await channel.pipeline.addHandler(sslHandler, position: .first).get()
} catch {
throw SMTPError.tlsUpgradeFailed
}
}
func sendRawBytes(_ data: Data) async throws {
guard let channel else { throw SMTPError.notConnected }
var buffer = channel.allocator.buffer(capacity: data.count)
buffer.writeBytes(data)
channel.writeAndFlush(buffer, promise: nil)
}
func disconnect() async throws {
try await channel?.close()
channel = nil
}
func shutdown() async throws {
try await group.shutdownGracefully()
}
}