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 }