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
Protocol()26 Protocol::Protocol()
27 :
28 fSocket(NULL),
29 fBufferedSocket(NULL),
30 fHandlerList(5, false),
31 fCommandID(0),
32 fIsConnected(false)
33 {
34 }
35
36
~Protocol()37 Protocol::~Protocol()
38 {
39 delete fSocket;
40 delete fBufferedSocket;
41 }
42
43
44 status_t
Connect(const BNetworkAddress & address,const char * username,const char * password,bool useSSL)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
Disconnect()121 Protocol::Disconnect()
122 {
123 if (IsConnected()) {
124 RawCommand command("LOGOUT");
125 ProcessCommand(command);
126 }
127 return _Disconnect();
128 }
129
130
131 bool
IsConnected()132 Protocol::IsConnected()
133 {
134 return fIsConnected;
135 }
136
137
138 bool
AddHandler(Handler & handler)139 Protocol::AddHandler(Handler& handler)
140 {
141 return fHandlerList.AddItem(&handler);
142 }
143
144
145 void
RemoveHandler(Handler & handler)146 Protocol::RemoveHandler(Handler& handler)
147 {
148 fHandlerList.RemoveItem(&handler);
149 }
150
151
152 status_t
GetFolders(FolderList & folders,BString & separator)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
GetSubscribedFolders(BStringList & folders,BString & separator)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
SubscribeFolder(const char * folder)214 Protocol::SubscribeFolder(const char* folder)
215 {
216 SubscribeCommand command(folder);
217 return ProcessCommand(command);
218 }
219
220
221 status_t
UnsubscribeFolder(const char * folder)222 Protocol::UnsubscribeFolder(const char* folder)
223 {
224 UnsubscribeCommand command(folder);
225 return ProcessCommand(command);
226 }
227
228
229 status_t
GetQuota(uint64 & used,uint64 & total)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
SendCommand(const char * command)247 Protocol::SendCommand(const char* command)
248 {
249 return SendCommand(0, command);
250 }
251
252
253 status_t
SendCommand(int32 id,const char * command)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
SendData(const char * buffer,uint32 length)275 Protocol::SendData(const char* buffer, uint32 length)
276 {
277 return fSocket->Write(buffer, length);
278 }
279
280
281 status_t
ProcessCommand(Command & command,bigtime_t timeout)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
HandleResponse(Command * command,bigtime_t timeout,bool disconnectOnTimeout)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
NextCommandID()370 Protocol::NextCommandID()
371 {
372 fCommandID++;
373 return fCommandID;
374 }
375
376
377 // #pragma mark - private
378
379
380 status_t
_Disconnect()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
_GetAllFolders(BStringList & folders)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
_ParseCapabilities(const ArgumentList & arguments)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