1 module smtp.mailsender; 2 3 import core.exception; 4 import core.sync.mutex; 5 6 import std.algorithm; 7 import std.conv; 8 import std.stdio; 9 import std.string; 10 import std.traits; 11 12 import smtp.client; 13 import smtp.message; 14 import smtp.reply; 15 16 version(ssl) { 17 import smtp.ssl; 18 } 19 20 /++ 21 High-level implementation of SMTP client. 22 +/ 23 class MailSender { 24 25 private: 26 SmtpClient _smtp_client; 27 28 version(ssl) { 29 EncryptionMethod _encType; 30 } 31 bool _server_supports_pipelining = false; 32 bool _server_supports_vrfy = false; 33 bool _server_supports_etrn = false; 34 bool _server_supports_enhanced_status_codes = false; 35 bool _server_supports_dsn = false; 36 bool _server_supports_8bitmime = false; 37 bool _server_supports_binarymime = false; 38 bool _server_supports_chunking = false; 39 bool _server_supports_encryption = false; 40 41 uint _max_message_size = 0; 42 43 Mutex _transmission_lock; 44 45 SmtpReply connect_impl() { 46 _transmission_lock.lock(); 47 scope(exit) _transmission_lock.unlock(); 48 auto reply = _smtp_client.connect(); 49 return reply; 50 } 51 52 SmtpReply get_server_capabilities() { 53 _transmission_lock.lock(); 54 scope(exit) _transmission_lock.unlock(); 55 // Trying to get what possibilities server supports 56 auto reply = _smtp_client.ehlo(); 57 foreach(line; split(strip(reply.message), "\r\n")[1 .. $ - 1]) { 58 auto extension = line[4 .. $]; 59 switch(extension) { 60 case "STARTTLS": 61 _server_supports_encryption = true; 62 break; 63 case "PIPELINING": 64 _server_supports_pipelining = true; 65 break; 66 case "VRFY": 67 _server_supports_vrfy = true; 68 break; 69 case "ETRN": 70 _server_supports_etrn = true; 71 break; 72 case "ENHANCEDSTATUSCODES": 73 _server_supports_enhanced_status_codes = true; 74 break; 75 case "DSN": 76 _server_supports_dsn = true; 77 break; 78 case "8BITMIME": 79 _server_supports_8bitmime = true; 80 break; 81 case "BINARYMIME": 82 _server_supports_binarymime = true; 83 break; 84 default: 85 } 86 } 87 88 foreach(line; split(strip(reply.message), "\r\n")[1 .. $]) { 89 auto option = line[4 .. $]; 90 if (option.startsWith("SIZE")) { 91 try { 92 _max_message_size = to!int(line[9 .. $]); 93 } catch (RangeError) { 94 } 95 continue; 96 } else if (option.startsWith("CHUNKING")) { 97 _server_supports_chunking = true; 98 continue; 99 } 100 } 101 return reply; 102 } 103 104 public: 105 version(ssl) { 106 /++ 107 SSL-enabled constructor 108 +/ 109 this(string host, ushort port, EncryptionMethod encType = EncryptionMethod.None) { 110 _smtp_client = new SmtpClient(host, port); 111 _encType = encType; 112 _transmission_lock = new Mutex(); 113 } 114 } else { 115 /++ 116 No-SSL constructor 117 +/ 118 this(string host, ushort port) { 119 _smtp_client = new SmtpClient(host, port); 120 _transmission_lock = new Mutex(); 121 } 122 } 123 124 /++ 125 Server limits 126 +/ 127 uint maxMessageSize() const { return _max_message_size; } 128 129 /++ 130 Server-supported extensions 131 +/ 132 bool extensionPipelining() const { return _server_supports_pipelining; } 133 bool extensionVrfy() const { return _server_supports_vrfy; } 134 bool extensionEtrn() const { return _server_supports_etrn; } 135 bool extensionEnhancedStatusCodes() const { return _server_supports_enhanced_status_codes; } 136 bool extensionDsn() const { return _server_supports_dsn; } 137 bool extension8bitMime() const { return _server_supports_8bitmime; } 138 bool extensionBinaryMime() const { return _server_supports_binarymime; } 139 bool extensionChunking() const { return _server_supports_chunking; } 140 bool extensionTls() const { return _server_supports_encryption; } 141 142 version(ssl){ 143 /++ 144 Connecting to SMTP server and also trying to get server possibiities 145 in order to expose it via public API. 146 +/ 147 SmtpReply connect() { 148 auto reply = connect_impl(); 149 if(!reply.success) return reply; 150 151 reply = get_server_capabilities(); 152 if(!reply.success) return reply; 153 154 _transmission_lock.lock(); 155 _smtp_client.starttls(); 156 _transmission_lock.unlock(); 157 158 reply = get_server_capabilities(); 159 return reply; 160 } 161 } else { 162 /++ 163 Connecting to SMTP server and also trying to get server possibiities 164 in order to expose it via public API. 165 +/ 166 SmtpReply connect() { 167 auto reply = connect_impl(); 168 if(!reply.success) return reply; 169 170 return get_server_capabilities(); 171 } 172 } 173 174 /++ 175 Perfrom authentication process in one method (high-level) instead 176 of sending AUTH and auth data in several messages. 177 178 Auth schemes accoring to type: 179 * PLAIN: 180 | AUTH->, <-STATUS, [encoded login/password]->, <-STATUS 181 * LOGIN: 182 | AUTH->, <-STATUS, [encoded login]->, <-STATUS, [encoded password]->, <-STATUS 183 +/ 184 SmtpReply authenticate(A...)(in SmtpAuthType authType, A params) { 185 _transmission_lock.lock(); 186 SmtpReply result; 187 final switch (authType) { 188 case SmtpAuthType.PLAIN: 189 static assert((params.length == 2) && is(A[0] == string) && is(A[1] == string)); 190 auto reply = _smtp_client.auth(authType); 191 result = reply.success ? _smtp_client.authPlain(params[0], params[1]) : reply; 192 break; 193 case SmtpAuthType.LOGIN: 194 static assert((params.length == 2) && is(A[0] == string) && is(A[1] == string)); 195 auto reply = _smtp_client.auth(authType); 196 if (reply.success) { 197 reply = _smtp_client.authLoginUsername(params[0]); 198 result = reply.success ? _smtp_client.authLoginPassword(params[1]) : reply; 199 } else { 200 result = reply; 201 } 202 break; 203 } 204 _transmission_lock.unlock(); 205 return result; 206 } 207 208 /++ 209 High-level method for sending messages. 210 211 Accepts SmtpMessage instance and returns true 212 if message was sent successfully or false otherwise. 213 214 This method is recommended in order to simplify the whole workflow 215 with the `smtp` library. 216 217 send method basically implements [mail -> rcpt ... rcpt -> data -> dataBody] 218 method calls chain. 219 +/ 220 SmtpReply send(in SmtpMessage mail) { 221 _transmission_lock.lock(); 222 auto reply = _smtp_client.mail(mail.sender.address); 223 if (!reply.success) return reply; 224 foreach (i, recipient; mail.recipients) { 225 reply = _smtp_client.rcpt(recipient.address); 226 if (!reply.success) { 227 _smtp_client.rset(); 228 _transmission_lock.unlock(); 229 return reply; 230 } 231 } 232 reply = _smtp_client.data(); 233 if (!reply.success) { 234 _smtp_client.rset(); 235 _transmission_lock.unlock(); 236 return reply; 237 } 238 reply = _smtp_client.dataBody(mail.toString()); 239 if (!reply.success) { 240 _smtp_client.rset(); 241 } 242 _transmission_lock.unlock(); 243 return reply; 244 } 245 246 /++ 247 High-level method for sending 'quit' message to SMTP server. 248 249 This method must be performed in order to notify server that 250 the client is going to finish its work with it. 251 +/ 252 SmtpReply quit() { 253 _transmission_lock.lock(); 254 auto reply = _smtp_client.quit(); 255 _transmission_lock.unlock(); 256 return reply; 257 } 258 259 /++ 260 Perform clean shutdown for allocated resources. 261 +/ 262 ~this() { 263 _smtp_client.disconnect(); 264 } 265 }