xref: /haiku/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Protocol.cpp (revision 0fe022fd0d6d600863555f5a556ab5a5273d20e0)
1 /*
2  * Copyright 2010-2015, 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.folder.ICompare("INBOX") == 0) {
171 				entry.subscribed = true;
172 				break;
173 			}
174 		}
175 		folders.push_back(entry);
176 	}
177 
178 	// you could be subscribed to a folder which not exist currently, add them:
179 	for (int32 i = 0; i < subscribedFolders.CountStrings(); i++) {
180 		bool isInlist = false;
181 		for (int32 j = 0; j < allFolders.CountStrings(); j++) {
182 			if (subscribedFolders.StringAt(i) == allFolders.StringAt(j)) {
183 				isInlist = true;
184 				break;
185 			}
186 		}
187 		if (isInlist)
188 			continue;
189 
190 		FolderEntry entry;
191 		entry.folder = subscribedFolders.StringAt(i);
192 		entry.subscribed = true;
193 		folders.push_back(entry);
194 	}
195 
196 	return B_OK;
197 }
198 
199 
200 status_t
201 Protocol::GetSubscribedFolders(BStringList& folders, BString& separator)
202 {
203 	ListCommand command(NULL, true);
204 	status_t status = ProcessCommand(command);
205 	if (status != B_OK)
206 		return status;
207 
208 	folders = command.FolderList();
209 	separator = command.Separator();
210 	return status;
211 }
212 
213 
214 status_t
215 Protocol::SubscribeFolder(const char* folder)
216 {
217 	SubscribeCommand command(folder);
218 	return ProcessCommand(command);
219 }
220 
221 
222 status_t
223 Protocol::UnsubscribeFolder(const char* folder)
224 {
225 	UnsubscribeCommand command(folder);
226 	return ProcessCommand(command);
227 }
228 
229 
230 status_t
231 Protocol::GetQuota(uint64& used, uint64& total)
232 {
233 	if (!Capabilities().Contains("QUOTA"))
234 		return B_ERROR;
235 
236 	GetQuotaCommand quotaCommand;
237 	status_t status = ProcessCommand(quotaCommand);
238 	if (status != B_OK)
239 		return status;
240 
241 	used = quotaCommand.UsedStorage();
242 	total = quotaCommand.TotalStorage();
243 	return B_OK;
244 }
245 
246 
247 status_t
248 Protocol::SendCommand(const char* command)
249 {
250 	return SendCommand(0, command);
251 }
252 
253 
254 status_t
255 Protocol::SendCommand(int32 id, const char* command)
256 {
257 	char buffer[2048];
258 	int32 length;
259 	if (id > 0) {
260 		length = snprintf(buffer, sizeof(buffer), "A%.7" B_PRId32 " %s\r\n",
261 			id, command);
262 	} else
263 		length = snprintf(buffer, sizeof(buffer), "%s\r\n", command);
264 
265 	TRACE("C: %s", buffer);
266 
267 	ssize_t bytesWritten = fSocket->Write(buffer, length);
268 	if (bytesWritten < 0)
269 		return bytesWritten;
270 
271 	return bytesWritten == length ? B_OK : B_ERROR;
272 }
273 
274 
275 ssize_t
276 Protocol::SendData(const char* buffer, uint32 length)
277 {
278 	return fSocket->Write(buffer, length);
279 }
280 
281 
282 status_t
283 Protocol::ProcessCommand(Command& command, bigtime_t timeout)
284 {
285 	BString commandString = command.CommandString();
286 	if (commandString.IsEmpty())
287 		return B_BAD_VALUE;
288 
289 	Handler* handler = dynamic_cast<Handler*>(&command);
290 	if (handler != NULL && !AddHandler(*handler))
291 		return B_NO_MEMORY;
292 
293 	int32 commandID = NextCommandID();
294 	status_t status = SendCommand(commandID, commandString.String());
295 	if (status == B_OK) {
296 		fOngoingCommands[commandID] = &command;
297 		status = HandleResponse(&command, timeout);
298 	}
299 
300 	if (handler != NULL)
301 		RemoveHandler(*handler);
302 
303 	return status;
304 }
305 
306 
307 // #pragma mark - protected
308 
309 
310 status_t
311 Protocol::HandleResponse(Command* command, bigtime_t timeout,
312 	bool disconnectOnTimeout)
313 {
314 	status_t commandStatus = B_OK;
315 	IMAP::ResponseParser parser(*fBufferedSocket);
316 	if (IMAP::LiteralHandler* literalHandler
317 			= dynamic_cast<IMAP::LiteralHandler*>(command))
318 		parser.SetLiteralHandler(literalHandler);
319 
320 	IMAP::Response response;
321 
322 	bool done = false;
323 	while (!done) {
324 		try {
325 			status_t status = parser.NextResponse(response, timeout);
326 			if (status != B_OK) {
327 				// we might have lost the connection, clear the connection state
328 				if (status != B_TIMED_OUT || disconnectOnTimeout)
329 					_Disconnect();
330 
331 				return status;
332 			}
333 
334 			if (response.IsUntagged() || response.IsContinuation()) {
335 				bool handled = false;
336 				for (int32 i = fHandlerList.CountItems(); i-- > 0;) {
337 					if (fHandlerList.ItemAt(i)->HandleUntagged(response)) {
338 						handled = true;
339 						break;
340 					}
341 				}
342 				if (!handled)
343 					printf("Unhandled S: %s\n", response.ToString().String());
344 			} else {
345 				CommandIDMap::iterator found
346 					= fOngoingCommands.find(response.Tag());
347 				if (found != fOngoingCommands.end()) {
348 					status_t status = found->second->HandleTagged(response);
349 					if (status != B_OK)
350 						commandStatus = status;
351 
352 					fOngoingCommands.erase(found);
353 				} else
354 					printf("Unknown tag S: %s\n", response.ToString().String());
355 			}
356 		} catch (ParseException& exception) {
357 			printf("Error during parsing: %s\n", exception.Message());
358 		}
359 
360 		if (fOngoingCommands.size() == 0)
361 			done = true;
362 	}
363 
364 	return commandStatus;
365 }
366 
367 
368 int32
369 Protocol::NextCommandID()
370 {
371 	fCommandID++;
372 	return fCommandID;
373 }
374 
375 
376 // #pragma mark - private
377 
378 
379 status_t
380 Protocol::_Disconnect()
381 {
382 	fOngoingCommands.clear();
383 	fIsConnected = false;
384 	delete fBufferedSocket;
385 	fBufferedSocket = NULL;
386 	delete fSocket;
387 	fSocket = NULL;
388 
389 	return B_OK;
390 }
391 
392 
393 status_t
394 Protocol::_GetAllFolders(BStringList& folders)
395 {
396 	ListCommand command(NULL, false);
397 	status_t status = ProcessCommand(command);
398 	if (status != B_OK)
399 		return status;
400 
401 	folders = command.FolderList();
402 	return status;
403 }
404 
405 
406 void
407 Protocol::_ParseCapabilities(const ArgumentList& arguments)
408 {
409 	fCapabilities.MakeEmpty();
410 
411 	for (int32 i = 0; i < arguments.CountItems(); i++) {
412 		if (StringArgument* argument
413 				= dynamic_cast<StringArgument*>(arguments.ItemAt(i)))
414 			fCapabilities.AddItem(new StringArgument(*argument));
415 	}
416 
417 	TRACE("capabilities: %s\n", fCapabilities.ToString().String());
418 }
419 
420 
421 }	// namespace IMAP
422