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