xref: /haiku/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Protocol.cpp (revision b8eeeb21e3ec3d4593da455008afcb72137070a9)
1 /*
2  * Copyright 2010-2016, Haiku Inc. All Rights Reserved.
3  * Copyright 2010 Clemens Zeidler. All rights reserved.
4  *
5  * Distributed under the terms of the MIT License.
6  */
7 
8 
9 #include "Protocol.h"
10 
11 #include "Commands.h"
12 
13 
14 #define DEBUG_IMAP_PROTOCOL
15 #ifdef DEBUG_IMAP_PROTOCOL
16 #	include <stdio.h>
17 #	define TRACE(...) printf(__VA_ARGS__)
18 #else
19 #	define TRACE(...) ;
20 #endif
21 
22 
23 namespace IMAP {
24 
25 
26 Protocol::Protocol()
27 	:
28 	fSocket(NULL),
29 	fBufferedSocket(NULL),
30 	fHandlerList(5, false),
31 	fCommandID(0),
32 	fIsConnected(false)
33 {
34 }
35 
36 
37 Protocol::~Protocol()
38 {
39 	delete fSocket;
40 	delete fBufferedSocket;
41 }
42 
43 
44 status_t
45 Protocol::Connect(const BNetworkAddress& address, const char* username,
46 	const char* password, bool useSSL)
47 {
48 	TRACE("Connect\n");
49 	if (useSSL)
50 		fSocket = new(std::nothrow) BSecureSocket(address);
51 	else
52 		fSocket = new(std::nothrow) BSocket(address);
53 
54 	if (fSocket == NULL)
55 		return B_NO_MEMORY;
56 
57 	status_t status = fSocket->InitCheck();
58 	if (status != B_OK)
59 		return status;
60 
61 	fBufferedSocket = new(std::nothrow) BBufferedDataIO(*fSocket, 32768, false,
62 		true);
63 	if (fBufferedSocket == NULL)
64 		return B_NO_MEMORY;
65 
66 	TRACE("Login\n");
67 
68 	fIsConnected = true;
69 
70 	LoginCommand login(username, password);
71 	status = ProcessCommand(login);
72 	if (status != B_OK) {
73 		_Disconnect();
74 		return status;
75 	}
76 
77 	_ParseCapabilities(login.Capabilities());
78 
79 	if (fCapabilities.IsEmpty()) {
80 		CapabilityHandler capabilityHandler;
81 		status = ProcessCommand(capabilityHandler);
82 		if (status != B_OK)
83 			return status;
84 
85 		_ParseCapabilities(capabilityHandler.Capabilities());
86 	}
87 
88 	if (Capabilities().Contains("ID")) {
89 		// Get the server's ID into our log
90 		class IDCommand : public IMAP::Command, public IMAP::Handler {
91 		public:
92 			BString CommandString()
93 			{
94 				return "ID NIL";
95 			}
96 
97 			bool HandleUntagged(IMAP::Response& response)
98 			{
99 				if (response.IsCommand("ID") && response.IsListAt(1)) {
100 					puts("Server:");
101 					ArgumentList& list = response.ListAt(1);
102 					for (int32 i = 0; i < list.CountItems(); i += 2) {
103 						printf("  %s: %s\n",
104 							list.ItemAt(i)->ToString().String(),
105 							list.ItemAt(i + 1)->ToString().String());
106 					}
107 					return true;
108 				}
109 
110 				return false;
111 			}
112 		};
113 		IDCommand idCommand;
114 		ProcessCommand(idCommand);
115 	}
116 	return B_OK;
117 }
118 
119 
120 status_t
121 Protocol::Disconnect()
122 {
123 	if (IsConnected()) {
124 		RawCommand command("LOGOUT");
125 		ProcessCommand(command);
126 	}
127 	return _Disconnect();
128 }
129 
130 
131 bool
132 Protocol::IsConnected()
133 {
134 	return fIsConnected;
135 }
136 
137 
138 bool
139 Protocol::AddHandler(Handler& handler)
140 {
141 	return fHandlerList.AddItem(&handler);
142 }
143 
144 
145 void
146 Protocol::RemoveHandler(Handler& handler)
147 {
148 	fHandlerList.RemoveItem(&handler);
149 }
150 
151 
152 status_t
153 Protocol::GetFolders(FolderList& folders, BString& separator)
154 {
155 	BStringList allFolders;
156 	status_t status = _GetAllFolders(allFolders);
157 	if (status != B_OK)
158 		return status;
159 
160 	BStringList subscribedFolders;
161 	status = GetSubscribedFolders(subscribedFolders, separator);
162 	if (status != B_OK)
163 		return status;
164 
165 	for (int32 i = 0; i < allFolders.CountStrings(); i++) {
166 		FolderEntry entry;
167 		entry.folder = allFolders.StringAt(i);
168 		for (int32 j = 0; j < subscribedFolders.CountStrings(); j++) {
169 			if (entry.folder == subscribedFolders.StringAt(j)) {
170 				entry.subscribed = true;
171 				break;
172 			}
173 		}
174 		folders.push_back(entry);
175 	}
176 
177 	// you could be subscribed to a folder which not exist currently, add them:
178 	for (int32 i = 0; i < subscribedFolders.CountStrings(); i++) {
179 		bool isInlist = false;
180 		for (int32 j = 0; j < allFolders.CountStrings(); j++) {
181 			if (subscribedFolders.StringAt(i) == allFolders.StringAt(j)) {
182 				isInlist = true;
183 				break;
184 			}
185 		}
186 		if (isInlist)
187 			continue;
188 
189 		FolderEntry entry;
190 		entry.folder = subscribedFolders.StringAt(i);
191 		entry.subscribed = true;
192 		folders.push_back(entry);
193 	}
194 
195 	return B_OK;
196 }
197 
198 
199 status_t
200 Protocol::GetSubscribedFolders(BStringList& folders, BString& separator)
201 {
202 	ListCommand command(NULL, true);
203 	status_t status = ProcessCommand(command);
204 	if (status != B_OK)
205 		return status;
206 
207 	folders = command.FolderList();
208 	separator = command.Separator();
209 	return status;
210 }
211 
212 
213 status_t
214 Protocol::SubscribeFolder(const char* folder)
215 {
216 	SubscribeCommand command(folder);
217 	return ProcessCommand(command);
218 }
219 
220 
221 status_t
222 Protocol::UnsubscribeFolder(const char* folder)
223 {
224 	UnsubscribeCommand command(folder);
225 	return ProcessCommand(command);
226 }
227 
228 
229 status_t
230 Protocol::GetQuota(uint64& used, uint64& total)
231 {
232 	if (!Capabilities().Contains("QUOTA"))
233 		return B_ERROR;
234 
235 	GetQuotaCommand quotaCommand;
236 	status_t status = ProcessCommand(quotaCommand);
237 	if (status != B_OK)
238 		return status;
239 
240 	used = quotaCommand.UsedStorage();
241 	total = quotaCommand.TotalStorage();
242 	return B_OK;
243 }
244 
245 
246 status_t
247 Protocol::SendCommand(const char* command)
248 {
249 	return SendCommand(0, command);
250 }
251 
252 
253 status_t
254 Protocol::SendCommand(int32 id, const char* command)
255 {
256 	char buffer[2048];
257 	int32 length;
258 	if (id > 0) {
259 		length = snprintf(buffer, sizeof(buffer), "A%.7" B_PRId32 " %s\r\n",
260 			id, command);
261 	} else
262 		length = snprintf(buffer, sizeof(buffer), "%s\r\n", command);
263 
264 	TRACE("C: %s", buffer);
265 
266 	ssize_t bytesWritten = fSocket->Write(buffer, length);
267 	if (bytesWritten < 0)
268 		return bytesWritten;
269 
270 	return bytesWritten == length ? B_OK : B_ERROR;
271 }
272 
273 
274 ssize_t
275 Protocol::SendData(const char* buffer, uint32 length)
276 {
277 	return fSocket->Write(buffer, length);
278 }
279 
280 
281 status_t
282 Protocol::ProcessCommand(Command& command, bigtime_t timeout)
283 {
284 	BString commandString = command.CommandString();
285 	if (commandString.IsEmpty())
286 		return B_BAD_VALUE;
287 
288 	Handler* handler = dynamic_cast<Handler*>(&command);
289 	if (handler != NULL && !AddHandler(*handler))
290 		return B_NO_MEMORY;
291 
292 	int32 commandID = NextCommandID();
293 	status_t status = SendCommand(commandID, commandString.String());
294 	if (status == B_OK) {
295 		fOngoingCommands[commandID] = &command;
296 		status = HandleResponse(&command, timeout);
297 	}
298 
299 	if (handler != NULL)
300 		RemoveHandler(*handler);
301 
302 	return status;
303 }
304 
305 
306 // #pragma mark - protected
307 
308 
309 status_t
310 Protocol::HandleResponse(Command* command, bigtime_t timeout,
311 	bool disconnectOnTimeout)
312 {
313 	status_t commandStatus = B_OK;
314 	IMAP::ResponseParser parser(*fBufferedSocket);
315 	if (IMAP::LiteralHandler* literalHandler
316 			= dynamic_cast<IMAP::LiteralHandler*>(command))
317 		parser.SetLiteralHandler(literalHandler);
318 
319 	IMAP::Response response;
320 
321 	bool done = false;
322 	while (!done) {
323 		try {
324 			status_t status = parser.NextResponse(response, timeout);
325 			if (status != B_OK) {
326 				// we might have lost the connection, clear the connection state
327 				if (status != B_TIMED_OUT || disconnectOnTimeout)
328 					_Disconnect();
329 
330 				return status;
331 			}
332 
333 			if (response.IsUntagged() || response.IsContinuation()) {
334 				bool handled = false;
335 				for (int32 i = fHandlerList.CountItems(); i-- > 0;) {
336 					if (fHandlerList.ItemAt(i)->HandleUntagged(response)) {
337 						handled = true;
338 						break;
339 					}
340 				}
341 				if (!handled)
342 					printf("Unhandled S: %s\n", response.ToString().String());
343 			} else {
344 				CommandIDMap::iterator found
345 					= fOngoingCommands.find(response.Tag());
346 				if (found != fOngoingCommands.end()) {
347 					status_t status = found->second->HandleTagged(response);
348 					if (status != B_OK)
349 						commandStatus = status;
350 
351 					fOngoingCommands.erase(found);
352 				} else
353 					printf("Unknown tag S: %s\n", response.ToString().String());
354 			}
355 		} catch (ParseException& exception) {
356 			printf("Error during parsing: %s\n", exception.Message());
357 		} catch (StreamException& exception) {
358 			return exception.Status();
359 		}
360 
361 		if (fOngoingCommands.size() == 0)
362 			done = true;
363 	}
364 
365 	return commandStatus;
366 }
367 
368 
369 int32
370 Protocol::NextCommandID()
371 {
372 	fCommandID++;
373 	return fCommandID;
374 }
375 
376 
377 // #pragma mark - private
378 
379 
380 status_t
381 Protocol::_Disconnect()
382 {
383 	fOngoingCommands.clear();
384 	fIsConnected = false;
385 	delete fBufferedSocket;
386 	fBufferedSocket = NULL;
387 	delete fSocket;
388 	fSocket = NULL;
389 
390 	return B_OK;
391 }
392 
393 
394 status_t
395 Protocol::_GetAllFolders(BStringList& folders)
396 {
397 	ListCommand command(NULL, false);
398 	status_t status = ProcessCommand(command);
399 	if (status != B_OK)
400 		return status;
401 
402 	folders = command.FolderList();
403 	return status;
404 }
405 
406 
407 void
408 Protocol::_ParseCapabilities(const ArgumentList& arguments)
409 {
410 	fCapabilities.MakeEmpty();
411 
412 	for (int32 i = 0; i < arguments.CountItems(); i++) {
413 		if (StringArgument* argument
414 				= dynamic_cast<StringArgument*>(arguments.ItemAt(i)))
415 			fCapabilities.AddItem(new StringArgument(*argument));
416 	}
417 
418 	TRACE("capabilities: %s\n", fCapabilities.ToString().String());
419 }
420 
421 
422 }	// namespace IMAP
423