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