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 }