1 module client.client; 2 3 import core.thread : Thread; 4 import std.socket : Socket, AddressFamily, SocketType, ProtocolType, parseAddress, Address, SocketOSException; 5 import bmessage; 6 import std.stdio; 7 import std.json; 8 import std.string; 9 import client.mail; 10 import server.server; 11 import std.conv : to; 12 13 public final class ButterflyClient : Thread 14 { 15 /** 16 * The associated server 17 */ 18 private ButterflyServer server; 19 20 /** 21 * Socket of the client connection 22 */ 23 private Socket clientSocket; 24 25 /** 26 * Whether or not the server is active 27 */ 28 private bool active = true; 29 30 /** 31 * The type of connection 32 */ 33 private enum ClientType 34 { 35 SERVER, 36 CLIENT 37 } 38 39 private ClientType connectionType; 40 41 /** 42 * The Mailbox (if client) of the connected 43 * user. 44 */ 45 private Mailbox mailbox; 46 47 this(ButterflyServer server, Socket clientSocket) 48 { 49 super(&run); 50 this.clientSocket = clientSocket; 51 this.server = server; 52 } 53 54 private void run() 55 { 56 /* The received command block */ 57 JSONValue commandBlock; 58 59 /* The received bytes */ 60 byte[] receivedBytes; 61 62 /* The JSON response to be sent */ 63 JSONValue responseBlock; 64 long status = 0; 65 66 /** 67 * TODO: My error handling in bformat is not good. 68 * A dead connection sitll returns successful write. 69 */ 70 71 /* TODO: Implement loop read-write here */ 72 while(active) 73 { 74 writeln("Awaiting command from client..."); 75 76 /* Await a message from the client */ 77 bool recvStatus = receiveMessage(clientSocket, receivedBytes); 78 writeln(recvStatus); 79 80 if(recvStatus) 81 { 82 /* TODO: Add error handling catch for all JSON here */ 83 84 /* Parse the incoming JSON */ 85 commandBlock = parseJSON(cast(string)receivedBytes); 86 writeln("Received response: "~commandBlock.toPrettyString()); 87 88 /* Get the command */ 89 string command = commandBlock["command"].str(); 90 91 /* TODO: Add command handling here */ 92 if(cmp(command, "authenticate") == 0) 93 { 94 /* Get the username and password */ 95 string authUsername = commandBlock["request"]["username"].str(); 96 string authPassword = commandBlock["request"]["password"].str(); 97 98 /* TODO: Implement authentication */ 99 bool authStatus = authenticate(authUsername, authPassword); 100 101 if(authStatus) 102 { 103 /** 104 * If the auth if successful then upgrade to 105 * a client-type connection. 106 */ 107 connectionType = ClientType.CLIENT; 108 109 /** 110 * Set the user's associated Mailbox up 111 */ 112 mailbox = new Mailbox(authUsername); 113 } 114 else 115 { 116 /* TODO: Error handling for authentication failure */ 117 } 118 } 119 /* TODO: Add command handling here */ 120 else if(cmp(command, "register") == 0) 121 { 122 /* Get the username and password */ 123 string regUsername = commandBlock["request"]["username"].str(); 124 string regPassword = commandBlock["request"]["password"].str(); 125 126 /* TODO: Implement registration */ 127 bool regStatus = register(regUsername, regPassword); 128 129 if(!regStatus) 130 { 131 /* TODO: Implement error handling for failed registration */ 132 } 133 } 134 else if(cmp(command, "sendMail") == 0) 135 { 136 /* Make sure the connection is from a client */ 137 if(connectionType == ClientType.CLIENT) 138 { 139 /* TODO: Implement me */ 140 141 /* Get the mail block */ 142 JSONValue mailBlock = commandBlock["request"]["mail"]; 143 144 /* Send the mail message */ 145 sendMail(mailBlock); 146 } 147 else 148 { 149 /* TODO: Add error handling */ 150 } 151 } 152 else if(cmp(command, "storeMail") == 0) 153 { 154 /* Make sure the connection is from a client */ 155 if(connectionType == ClientType.CLIENT) 156 { 157 /* Get the mail block */ 158 JSONValue mailBlock = commandBlock["request"]["mail"]; 159 160 /* Get the folder to store the mail message in */ 161 Folder storeFolder = new Folder(mailbox, commandBlock["request"]["folder"].str()); 162 163 /* Store the message in the mailbox */ 164 storeMail(storeFolder, mailBlock); 165 } 166 else 167 { 168 /* TODO: Add error handling */ 169 } 170 } 171 else if(cmp(command, "deliverMail") == 0) 172 { 173 /* Make sure the connection is from a server */ 174 if(connectionType == ClientType.SERVER) 175 { 176 /* TODO: Implement me */ 177 deliverMail(commandBlock["request"]["mail"]); 178 } 179 else 180 { 181 /* TODO: Add error handling */ 182 } 183 } 184 else if(cmp(command, "fetchMail") == 0) 185 { 186 /* Make sure the connection is from a client */ 187 if(connectionType == ClientType.CLIENT) 188 { 189 /* TODO: Implement me */ 190 191 /* The folder where the mail message is stored */ 192 Folder fetchFolder = new Folder(mailbox, commandBlock["request"]["folder"].str()); 193 194 /* The mail ID of the mail message */ 195 string mailID = commandBlock["request"]["id"].str(); 196 197 /* Fetch the Mail */ 198 Mail fetchedMail = new Mail(mailbox, fetchFolder, mailID); 199 200 /* Set the response */ 201 JSONValue response; 202 response["mail"] = fetchedMail.getMessage(); 203 responseBlock["response"] = response; 204 } 205 else 206 { 207 /* TODO: Add error handling */ 208 } 209 } 210 else if(cmp(command, "createFolder") == 0) 211 { 212 /* Make sure the connection is from a client */ 213 if(connectionType == ClientType.CLIENT) 214 { 215 /* TODO: Implement me */ 216 createFolder(commandBlock["request"]["folderName"].str()); 217 } 218 else 219 { 220 /* TODO: Add error handling */ 221 } 222 } 223 else if(cmp(command, "deleteFolder") == 0) 224 { 225 /* Make sure the connection is from a client */ 226 if(connectionType == ClientType.CLIENT) 227 { 228 /* TODO: Implement me */ 229 } 230 else 231 { 232 /* TODO: Add error handling */ 233 } 234 } 235 else if(cmp(command, "addToFolder") == 0) 236 { 237 /* Make sure the connection is from a client */ 238 if(connectionType == ClientType.CLIENT) 239 { 240 /* TODO: Implement me */ 241 } 242 else 243 { 244 /* TODO: Add error handling */ 245 } 246 } 247 else if(cmp(command, "removeFromFolder") == 0) 248 { 249 /* Make sure the connection is from a client */ 250 if(connectionType == ClientType.CLIENT) 251 { 252 /* TODO: Implement me */ 253 } 254 else 255 { 256 /* TODO: Add error handling */ 257 } 258 } 259 else if(cmp(command, "listMail") == 0) 260 { 261 /* Make sure the connection is from a client */ 262 if(connectionType == ClientType.CLIENT) 263 { 264 /* Get the folder wanting to be listed */ 265 Folder listFolder = new Folder(mailbox, commandBlock["request"]["folderName"].str()); 266 267 responseBlock["mailIDs"] = to!(string)(listFolder.getMessages()); 268 } 269 else 270 { 271 /* TODO: Add error handling */ 272 } 273 } 274 else if(cmp(command, "listFolder") == 0) 275 { 276 /* Make sure the connection is from a client */ 277 if(connectionType == ClientType.CLIENT) 278 { 279 /* Get the folder wanting to be listed */ 280 Folder listFolder = new Folder(mailbox, commandBlock["request"]["folderName"].str()); 281 282 //responseBlock["folders"] = to!(string)(listFolder.getMessages()); 283 } 284 else 285 { 286 /* TODO: Add error handling */ 287 } 288 } 289 290 else if(cmp(command, "totsiens") == 0) 291 { 292 /* Close the connection on next loop condition check */ 293 active = false; 294 } 295 else 296 { 297 /* TODO: Add error handling for invalid commands */ 298 } 299 } 300 else 301 { 302 /* TODO: Add error handling here */ 303 } 304 305 /* TODO: Write response here */ 306 307 /* Generate the `status` field */ 308 responseBlock["status"] = status; 309 310 /* Write the response block to the client */ 311 writeln("Writing back response: "~responseBlock.toPrettyString()); 312 bool sendStatus = sendMessage(clientSocket, cast(byte[])toJSON(responseBlock)); 313 writeln(sendStatus); 314 315 /* If there was an error writing the response back */ 316 if(!sendStatus) 317 { 318 /* End the session */ 319 active = false; 320 writeln("Response write back failed"); 321 } 322 } 323 324 writeln("Closing session..."); 325 326 /* Close the socket */ 327 clientSocket.close(); 328 } 329 330 /** 331 * Stores a mail message in the users Mailbox 332 * at in the given Folder, `folder`. 333 */ 334 private Mail storeMail(Folder folder, JSONValue mailBlock) 335 { 336 /* Create the Mail message to store it */ 337 Mail savedMail = Mail.createMail(mailbox, folder, mailBlock); 338 339 return savedMail; 340 } 341 342 private bool authenticate(string username, string password) 343 { 344 /* TODO: Implement me */ 345 346 return true; 347 } 348 349 private bool register(string username, string password) 350 { 351 /* TODO Implement me */ 352 353 /* Return false in the case that registration fails */ 354 if(Mailbox.isMailbox(username)) 355 { 356 return false; 357 } 358 359 Mailbox.createMailbox(username); 360 361 return true; 362 } 363 364 /** 365 * Create a folder in your mailbox 366 */ 367 private Folder createFolder(string folderName) 368 { 369 /* The newly created Folder */ 370 Folder newFolder; 371 372 /** 373 * Check if we are creating a base folder 374 * or a nested folder. 375 */ 376 bool isBaseFolder = true; /* TODO: Logic work out */ 377 378 /* If it is a base folder wanting to be created */ 379 if(isBaseFolder) 380 { 381 newFolder = mailbox.addBaseFolder(folderName); 382 } 383 /* If it is a nested folder wanting to be created */ 384 else 385 { 386 //newFolder = ; 387 } 388 389 return newFolder; 390 } 391 392 /** 393 * Delivers the mail to the local users 394 */ 395 private void deliverMail(JSONValue mailBlock) 396 { 397 /* Get a list of the recipients of the mail message */ 398 string[] recipients; 399 foreach(JSONValue recipient; mailBlock["recipients"].array()) 400 { 401 recipients ~= recipient.str(); 402 } 403 404 /* Store the mail to each of the recipients */ 405 foreach(string recipient; recipients) 406 { 407 /* Get the mail address */ 408 string[] mailAddress = split(recipient, "@"); 409 410 /* Get the username */ 411 string username = mailAddress[0]; 412 413 /* Get the domain */ 414 string domain = mailAddress[1]; 415 416 /** 417 * Check if the domain of this recipient is this server 418 * or if it is a remote server. 419 */ 420 if(cmp(domain, server.domain) == 0) 421 { 422 writeln("Storing mail message to "~recipient~" ..."); 423 424 /* Get the Mailbox of a given user */ 425 Mailbox userMailbox = new Mailbox(username); 426 427 /* Get the Inbox folder */ 428 Folder inboxFolder = new Folder(userMailbox, "Inbox"); 429 430 /* Store the message in their Inbox folder */ 431 Mail.createMail(userMailbox, inboxFolder, mailBlock); 432 433 writeln("Stored mail message"); 434 } 435 } 436 } 437 438 /** 439 * Sends the mail message `mail` to the servers 440 * listed in the recipients field. 441 */ 442 private void sendMail(JSONValue mailBlock) 443 { 444 /* Get a list of the recipients of the mail message */ 445 string[] recipients; 446 foreach(JSONValue recipient; mailBlock["recipients"].array()) 447 { 448 recipients ~= recipient.str(); 449 } 450 451 /* Send the mail to each of the recipients */ 452 foreach(string recipient; recipients) 453 { 454 writeln("Sending mail message to "~recipient~" ..."); 455 456 /* Get the mail address */ 457 string[] mailAddress = split(recipient, "@"); 458 459 /* Get the username */ 460 string username = mailAddress[0]; 461 462 /* Get the domain */ 463 string domain = mailAddress[1]; 464 465 /** 466 * Check if the domain of this recipient is this server 467 * or if it is a remote server. 468 */ 469 if(cmp(domain, server.domain) == 0) 470 { 471 writeln("Local delivery occurring..."); 472 473 /* Get the Mailbox of a given user */ 474 Mailbox userMailbox = new Mailbox(username); 475 476 /* Get the Inbox folder */ 477 Folder inboxFolder = new Folder(userMailbox, "Inbox"); 478 479 /* Store the message in their Inbox folder */ 480 Mail.createMail(userMailbox, inboxFolder, mailBlock); 481 } 482 else 483 { 484 /* TODO: Do remote mail delivery */ 485 writeln("Remote delivery occurring..."); 486 487 /** 488 * Construct the server message to send to the 489 * remote server. 490 */ 491 JSONValue messageBlock; 492 messageBlock["command"] = "deliverMail"; 493 494 JSONValue requestBlock; 495 requestBlock["mail"] = mailBlock; 496 messageBlock["request"] = requestBlock; 497 498 /* Deliver the mail to the remote server */ 499 Socket remoteServer = new Socket(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP); 500 501 try 502 { 503 remoteServer.connect(parseAddress(domain, 6969)); 504 bool sendStatus = sendMessage(remoteServer, cast(byte[])toJSON(messageBlock)); 505 506 if(!sendStatus) 507 { 508 goto deliveryFailed; 509 } 510 511 byte[] receivedBytes; 512 bool recvStatus = receiveMessage(clientSocket, receivedBytes); 513 514 if(!recvStatus) 515 { 516 goto deliveryFailed; 517 } 518 519 /* Close the connection with the remote host */ 520 remoteServer.close(); 521 522 JSONValue responseBlock = parseJSON(cast(string)receivedBytes); 523 524 /* TODO: Get status code here an act on it */ 525 if(responseBlock["status"].integer() == 0) 526 { 527 writeln("Message delivered to server "~domain); 528 } 529 else 530 { 531 goto deliveryFailed; 532 } 533 } 534 catch(SocketOSException) 535 { 536 goto deliveryFailed; 537 } 538 catch(JSONException) 539 { 540 deliveryFailed: 541 writeln("Error delivering to server "~domain); 542 continue; 543 } 544 } 545 546 writeln("Sent mail message"); 547 } 548 549 writeln("Mail delivered"); 550 551 /* Store the message in this user's "Sent" folder */ 552 Folder sentFolder = new Folder(mailbox, "Sent"); 553 554 /* Store the message in their Inbox folder */ 555 Mail.createMail(mailbox, sentFolder, mailBlock); 556 557 writeln("Saved mail message to sent folder"); 558 } 559 }