1 ///
2 module imap.session;
3 import imap.defines;
4 import imap.socket;
5 import imap.set;
6 import imap.sil : SILdoc;
7 
8 import core.stdc.stdio;
9 import core.stdc.string;
10 import core.stdc.errno;
11 import std.socket;
12 import core.time : Duration;
13 
14 import deimos.openssl.ssl;
15 import deimos.openssl.err;
16 import deimos.openssl.sha;
17 
18 
19 struct SSL_
20 {
21 	SSL* handle;
22 	alias handle this;
23 }
24 
25 
26 ///
27 Set!T removeValues(T)(Set!T set, T[] values)
28 {
29 	import std.algorithm : each;
30 	Set!T ret;
31 	set.values_.byKeyValue.each!(entry => ret[entry.key] = entry.value);
32 	foreach(value;values)
33 	{
34 		if (value in ret)
35 			ret.remove(value);
36 	}
37 	return ret;
38 }
39 
40 ///
41 Set!T addValues(T)(Set!T set, T[] values)
42 {
43 	import std.algorithm : each;
44 	Set!T ret;
45 	set.values_.byKeyValue.each!(entry => ret[entry.key] = entry.value);
46 	values.each!(value => set.values_[value]=true);
47 	return ret;
48 }
49 
50 ///
51 Set!T addSet(T)(Set!T lhs, Set!T rhs)
52 {
53 	import std.algorithm : each;
54 	Set!T ret;
55 	return lhs.addValues(rhs.values);
56 }
57 
58 ///
59 Set!T removeSet(T)(Set!T lhs, Set!T rhs)
60 {
61 	import std.algorithm : each;
62 	Set!T ret;
63 	return lhs.removeValues(rhs.values);
64 }
65 
66 ///
67 struct ImapServer
68 {
69 	string server = "imap.fastmail.com"; // localhost";
70 	string port = "993";
71 
72     string toString()
73     {
74         import std.format : format;
75         return format!"%s:%s"(server,port);
76     }
77 }
78 
79 ///
80 struct  ImapLogin
81 {
82 	@SILdoc("User's name. It takes a string as a value.")
83 	string username = "laeeth@kaleidic.io";
84 
85 	@SILdoc("User's secret keyword. If a password wasn't supplied the user will be asked to enter one interactively the first time it will be needed. It takes a string as a value.")
86 	string password;
87 
88     string toString()
89     {
90         import std.format : format;
91         return format!"%s:[hidden]"(username);
92     }
93 }
94 
95 ///
96 struct Options
97 {
98 	import core.time : Duration, seconds, minutes;
99 
100 	bool debugMode = true;
101 	bool verboseOutput = true;
102 	bool interactive = false;
103 	bool namespace = false;
104 
105 	@SILdoc("When this option is enabled and the server supports the Challenge-Response Authentication Mechanism (specifically CRAM-MD5), this method will be used for user authentication instead of a plaintext password LOGIN. This variable takes a boolean as a value. Default is false")
106 	bool cramMD5 = false;
107 
108 	bool startTLS = false;
109 	bool tryCreate = false;
110 	bool recoverAll = true;
111 	bool recoverErrors = true;
112 
113 	@SILdoc("Normally, messages are marked for deletion and are actually deleted when the mailbox is closed. When this option is enabled, messages are expunged immediately after being marked deleted. This variable takes a boolean as a value. Default is false")
114 	bool expunge = false;
115 
116 	@SILdoc("By enabling this option new mailboxes that were automatically created, get also subscribed; they are set active in order for IMAP clients to recognize them. This variable takes a boolean as a value. Default is false")
117 	bool subscribe = false;
118 
119 	bool wakeOnAny = true;
120 
121 	@SILdoc("The time in minutes before terminating and re-issuing the IDLE command, in order to keep alive the connection, by resetting the inactivity timeout of the server. A standards compliant server must have an inactivity timeout of at least 30 minutes. But it may happen that some IMAP servers don't respect that, or some intermediary network device has a shorter timeout. By setting this option the above problem can be worked around. This variable takes a number as a value. Default is 29 minutes. ")
122 	Duration keepAlive = 29.minutes;
123 
124 	string logFile;
125 	string configFile;
126 	string oneline;
127 	string trustStore = "/etc/ssl/certs";
128 	string trustFile = "/etc/ssl/certs/cert.pem";
129 	Duration timeout = 20.seconds;
130 }
131 
132 ///
133 struct Session
134 {
135 	import imap.defines : ImapStatus;
136 	import imap.namespace;
137 	Options options;
138 	ImapStatus status_;
139 	string server;
140 
141 	@SILdoc("The port to connect to. It takes a number as a value. Default is ''143'' for imap and ''993'' for imaps.")
142 	string port;
143 
144 	package AddressInfo addressInfo;
145 	ImapLogin imapLogin;
146 	Socket socket;
147 	ImapProtocol imapProtocol;
148 	Set!Capability capabilities;
149 	string namespacePrefix;
150 	char namespaceDelim = '\0';
151 	Mailbox selected;
152 
153 	bool useSSL = true;
154 	bool noCerts = true;
155 	ProtocolSSL sslProtocol = ProtocolSSL.tls1_2; // ssl3; // tls1_2;
156 	SSL* sslConnection;
157 	SSL_CTX* sslContext;
158 
159     string toString()
160     {
161         import std.array : Appender;
162         import std.format : format;
163         import std.conv : to;
164         Appender!string ret;
165         ret.put(format!"Session to %s:%s as user %s\n"(server,port,imapLogin.username));
166         ret.put(format!"- useSSL: %s\n"(useSSL.to!string));
167         ret.put(format!"- startTLS: %s\n"(useSSL.to!string));
168         ret.put(format!"- noCerts: %s\n"(noCerts.to!string));
169         ret.put(format!"- sslProtocol: %s\n"(sslProtocol.to!string));
170         ret.put(format!"- imap protocol: %s\n"(imapProtocol.to!string));
171         ret.put(format!" - capabilities: %s\n"(capabilities.to!string));
172         ret.put(format!" - namespace: %s/%s\n"(namespacePrefix,[namespaceDelim]));
173         ret.put(format!" - selected mailbox: %s\n"(selected));
174         return ret.data;
175     }
176 
177 	this(ImapServer imapServer,ImapLogin imapLogin, bool useSSL = true, Options options = Options.init)
178 	{
179 		import std.exception : enforce;
180 		import std.process : environment;
181 		this.options = options;
182 		this.server = imapServer.server;
183 		this.port = imapServer.port;
184         this.useSSL = useSSL;
185 		this.imapLogin = imapLogin;
186 	}
187 
188 	ref Session useStartTLS(bool useTLS = true)
189 	{
190 		this.options.startTLS = useTLS;
191 		return this;
192 	}
193 
194 	ref Session setSelected(Mailbox mailbox)
195 	{
196 		this.selected = mailbox;
197 		return this;
198 	}
199 
200 	ref Session setStatus(ImapStatus status)
201 	{
202 		this.status_ = status;
203 		return this;
204 	}
205 
206 	string status()
207 	{
208 		import std.conv : to;
209 		return status_.to!string;
210 	}
211 }
212