Compare commits
41 Commits
Author | SHA1 | Date |
---|---|---|
Tristan B. Kildaire | 5a9896613c | |
Tristan B. Kildaire | 98c6208fe7 | |
Tristan B. Kildaire | 89db434578 | |
Tristan B. Kildaire | b4501b5da1 | |
Tristan B. Kildaire | 16aa7ac450 | |
Tristan B. Kildaire | 2fb72f253b | |
Tristan B. Kildaire | 17c6e5ce08 | |
Tristan B. Kildaire | a3d240cb83 | |
Tristan B. Kildaire | 436fe3b803 | |
Tristan B. Kildaire | c044ec5b31 | |
Tristan B. Kildaire | 4ab48120fa | |
Tristan B. Kildaire | a9c2e0680f | |
Tristan B. Kildaire | 75faa5d564 | |
Tristan B. Kildaire | 25961601bc | |
Tristan B. Kildaire | cc832f151b | |
Tristan B. Kildaire | 755d86cf1e | |
Tristan B. Kildaire | 2553978839 | |
Tristan B. Kildaire | 738eb9397b | |
Tristan B. Kildaire | 65a5ece0c0 | |
Tristan B. Kildaire | d8b32368e7 | |
Tristan B. Kildaire | fbfc45ec09 | |
Tristan B. Kildaire | 0bf0af860a | |
Tristan B. Kildaire | d912721723 | |
Tristan B. Kildaire | b55921fea6 | |
Tristan B. Kildaire | 0392752b01 | |
Tristan B. Kildaire | ae944f5647 | |
Tristan B. Kildaire | e31a6f09b8 | |
Tristan B. Kildaire | d091800ce7 | |
Tristan B. Kildaire | 5d12378cd3 | |
Tristan B. Kildaire | 704200025c | |
Tristan B. Kildaire | 478cafd229 | |
Tristan B. Kildaire | 1e8cd9117d | |
Tristan B. Kildaire | ed2694bf35 | |
Tristan B. Kildaire | e9a5af7863 | |
Tristan B. Kildaire | 785113e5f8 | |
Tristan B. Kildaire | 1d50c30e29 | |
Tristan B. Kildaire | 5ea1ac2ba5 | |
Tristan B. Kildaire | c23214b186 | |
Tristan B. Kildaire | c7398586ea | |
Tristan B. Kildaire | e49f8fa9fd | |
Tristan B. Kildaire | 0326b61e34 |
|
@ -1,5 +1,17 @@
|
|||
{
|
||||
"domain" : "10.0.0.9:2222",
|
||||
"address" : "0.0.0.0",
|
||||
"port" : "6969"
|
||||
"listeners" : {
|
||||
"enabled" : ["listener1"],
|
||||
"listener1" : {
|
||||
"type" : "ipv4",
|
||||
"domain" : "10.1.0.4:6969",
|
||||
"address" : "0.0.0.0",
|
||||
"port" : "6969"
|
||||
},
|
||||
"listener2" : {
|
||||
"type" : "ipv6",
|
||||
"domain" : "10.0.0.9:2222",
|
||||
"address" : "::",
|
||||
"port" : "6969"
|
||||
}
|
||||
}
|
||||
}
|
4
dub.json
4
dub.json
|
@ -4,7 +4,9 @@
|
|||
],
|
||||
"copyright": "Copyright © 2020, Tristan B. Kildaire",
|
||||
"dependencies": {
|
||||
"bformat": "1.0.8"
|
||||
"bformat": "3.1.0",
|
||||
"gogga": "~>0.0.2",
|
||||
"tristanable": "2.2.0"
|
||||
},
|
||||
"description": "Butterfly mail daemon",
|
||||
"license": "AGPL v3",
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"fileVersion": 1,
|
||||
"versions": {
|
||||
"bformat": "1.0.8"
|
||||
"bformat": "3.1.0",
|
||||
"gogga": "0.0.2",
|
||||
"tristanable": "2.2.0"
|
||||
}
|
||||
}
|
||||
|
|
44
source/app.d
44
source/app.d
|
@ -5,10 +5,14 @@ import std.socket : Address, parseAddress;
|
|||
import std.file;
|
||||
import std.json : JSONValue, parseJSON;
|
||||
import std.conv : to;
|
||||
import server.listener : ButterflyListener;
|
||||
import server.listeners;
|
||||
import std.string : cmp;
|
||||
import gogga;
|
||||
|
||||
void main()
|
||||
{
|
||||
writeln("Starting butterflyd...");
|
||||
gprintln("Starting butterflyd...");
|
||||
|
||||
JSONValue config;
|
||||
|
||||
|
@ -21,7 +25,41 @@ void main()
|
|||
|
||||
config = parseJSON(cast(string)bytes);
|
||||
|
||||
/* Construct the listeners form the config file */
|
||||
ButterflyListener[] listeners = constructListeners(config["listeners"]);
|
||||
|
||||
/* Create the server */
|
||||
ButterflyServer server = new ButterflyServer(listeners);
|
||||
|
||||
/* Start the server */
|
||||
Address address = parseAddress(config["address"].str(), to!(ushort)(config["port"].str()));
|
||||
ButterflyServer server = new ButterflyServer(address, config["domain"].str());
|
||||
server.run();
|
||||
}
|
||||
|
||||
private ButterflyListener[] constructListeners(JSONValue listenersBlock)
|
||||
{
|
||||
ButterflyListener[] listeners;
|
||||
|
||||
string[] enabledListeners;
|
||||
foreach(JSONValue listenerType; listenersBlock["enabled"].array())
|
||||
{
|
||||
enabledListeners ~= listenerType.str();
|
||||
}
|
||||
|
||||
foreach(string listener; enabledListeners)
|
||||
{
|
||||
gprintln("Constructing listener \"" ~ listener ~ "\" ...");
|
||||
|
||||
if(cmp(listenersBlock[listener]["type"].str(), "ipv4") == 0)
|
||||
{
|
||||
listeners ~= new IPv4Listener(listener, listenersBlock[listener]);
|
||||
}
|
||||
else if(cmp(listenersBlock[listener]["type"].str(), "ipv6") == 0)
|
||||
{
|
||||
listeners ~= new IPv6Listener(listener, listenersBlock[listener]);
|
||||
}
|
||||
|
||||
gprintln("Listener \"" ~ listener ~ "\" constructed");
|
||||
}
|
||||
|
||||
return listeners;
|
||||
}
|
||||
|
|
|
@ -13,13 +13,15 @@ import client.exceptions;
|
|||
import std.file;
|
||||
import std.exception;
|
||||
import std.datetime.systime : Clock, SysTime;
|
||||
import server.listener : ButterflyListener;
|
||||
import gogga;
|
||||
|
||||
public final class ButterflyClient : Thread
|
||||
{
|
||||
/**
|
||||
* The associated server
|
||||
* The associated listener
|
||||
*/
|
||||
private ButterflyServer server;
|
||||
private ButterflyListener listener;
|
||||
|
||||
/**
|
||||
* Socket of the client connection
|
||||
|
@ -48,11 +50,11 @@ public final class ButterflyClient : Thread
|
|||
*/
|
||||
private Mailbox mailbox;
|
||||
|
||||
this(ButterflyServer server, Socket clientSocket)
|
||||
this(ButterflyListener listener, Socket clientSocket)
|
||||
{
|
||||
super(&run);
|
||||
this.clientSocket = clientSocket;
|
||||
this.server = server;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private void run()
|
||||
|
@ -76,11 +78,11 @@ public final class ButterflyClient : Thread
|
|||
/* TODO: Implement loop read-write here */
|
||||
while(active)
|
||||
{
|
||||
writeln("Awaiting command from client...");
|
||||
gprintln("Awaiting command from client...");
|
||||
|
||||
/* Await a message from the client */
|
||||
bool recvStatus = receiveMessage(clientSocket, receivedBytes);
|
||||
writeln(recvStatus);
|
||||
gprintln(recvStatus);
|
||||
|
||||
/* If the receive succeeded */
|
||||
if(recvStatus)
|
||||
|
@ -96,7 +98,7 @@ public final class ButterflyClient : Thread
|
|||
{
|
||||
/* Parse the incoming JSON */
|
||||
commandBlock = parseJSON(cast(string)receivedBytes);
|
||||
writeln("Received response: "~commandBlock.toPrettyString());
|
||||
gprintln("Received response: "~commandBlock.toPrettyString());
|
||||
|
||||
/* Get the command */
|
||||
string command = commandBlock["command"].str();
|
||||
|
@ -169,7 +171,36 @@ public final class ButterflyClient : Thread
|
|||
Folder storeFolder = new Folder(mailbox, commandBlock["request"]["folder"].str());
|
||||
|
||||
/* Store the message in the mailbox */
|
||||
storeMail(storeFolder, mailBlock);
|
||||
Mail storedMail = storeMail(storeFolder, mailBlock);
|
||||
|
||||
/* Set the response to be the mail message's ID */
|
||||
JSONValue response;
|
||||
response["mailID"] = storedMail.getMailID();
|
||||
responseBlock["response"] = response;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* TODO: Add error handling */
|
||||
}
|
||||
}
|
||||
else if(cmp(command, "editMail") == 0)
|
||||
{
|
||||
/* Make sure the connection is from a client */
|
||||
if(connectionType == ClientType.CLIENT)
|
||||
{
|
||||
/* Get the mail block */
|
||||
JSONValue mailBlock = commandBlock["request"]["mail"];
|
||||
|
||||
/* Get the folder the mail message wanting to be edited resides in */
|
||||
Folder storeFolder = new Folder(mailbox, commandBlock["request"]["folder"].str());
|
||||
|
||||
/* Get the mail message wanting to be edited */
|
||||
Mail messageOriginal = new Mail(mailbox, storeFolder, commandBlock["request"]["mailID"].str());
|
||||
|
||||
/* Update the message with the new data */
|
||||
Mail updatedMail = editMail(messageOriginal, storeFolder, mailBlock);
|
||||
|
||||
responseBlock["response"]["mailID"] = updatedMail.getMailID();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -218,7 +249,7 @@ public final class ButterflyClient : Thread
|
|||
/* Make sure the connection is from a client */
|
||||
if(connectionType == ClientType.CLIENT)
|
||||
{
|
||||
/* TODO: Implement me */
|
||||
/* Create the new folder */
|
||||
createFolder(commandBlock["request"]["folderName"].str());
|
||||
}
|
||||
else
|
||||
|
@ -231,14 +262,36 @@ public final class ButterflyClient : Thread
|
|||
/* Make sure the connection is from a client */
|
||||
if(connectionType == ClientType.CLIENT)
|
||||
{
|
||||
/* TODO: Implement me */
|
||||
/* The folder to be deleted */
|
||||
Folder deleteFolder = new Folder(mailbox, commandBlock["request"]["folder"].str());
|
||||
|
||||
/* Delete the folder */
|
||||
deleteFolder.deleteFolder();
|
||||
}
|
||||
else
|
||||
{
|
||||
/* TODO: Add error handling */
|
||||
}
|
||||
}
|
||||
else if(cmp(command, "addToFolder") == 0)
|
||||
else if(cmp(command, "deleteMail") == 0)
|
||||
{
|
||||
/* Make sure the connection is from a client */
|
||||
if(connectionType == ClientType.CLIENT)
|
||||
{
|
||||
/* The folder the mail wanting to be deleted resides in */
|
||||
Folder mailDirectory = new Folder(mailbox, commandBlock["request"]["folder"].str());
|
||||
|
||||
/* The mail message to be deleted */
|
||||
Mail mailToDelete = new Mail(mailbox, mailDirectory, commandBlock["request"]["mailID"].str());
|
||||
|
||||
mailToDelete.deleteMessage();
|
||||
}
|
||||
else
|
||||
{
|
||||
/* TODO: Add error handling */
|
||||
}
|
||||
}
|
||||
else if(cmp(command, "moveFolder") == 0)
|
||||
{
|
||||
/* Make sure the connection is from a client */
|
||||
if(connectionType == ClientType.CLIENT)
|
||||
|
@ -250,12 +303,27 @@ public final class ButterflyClient : Thread
|
|||
/* TODO: Add error handling */
|
||||
}
|
||||
}
|
||||
else if(cmp(command, "removeFromFolder") == 0)
|
||||
else if(cmp(command, "moveMail") == 0)
|
||||
{
|
||||
/* Make sure the connection is from a client */
|
||||
if(connectionType == ClientType.CLIENT)
|
||||
{
|
||||
/* TODO: Implement me */
|
||||
/* The folder of the original mail message */
|
||||
Folder originalMessageFolder = new Folder(mailbox, commandBlock["request"]["originalFolder"].str());
|
||||
|
||||
/* The original mail message */
|
||||
Mail originalMailMessage = new Mail(mailbox, originalMessageFolder, commandBlock["request"]["mailID"].str());
|
||||
|
||||
/* The folder to move the mail message to */
|
||||
Folder newMailFolder = new Folder(mailbox, commandBlock["request"]["newFolder"].str());
|
||||
|
||||
/* Move mail message */
|
||||
Mail newMail = moveMail(originalMessageFolder, originalMailMessage, newMailFolder);
|
||||
|
||||
/* Set the response */
|
||||
JSONValue response;
|
||||
response["mailID"] = newMail.getMailID();
|
||||
responseBlock["response"] = response;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -271,7 +339,9 @@ public final class ButterflyClient : Thread
|
|||
Folder listFolder = new Folder(mailbox, commandBlock["request"]["folderName"].str());
|
||||
|
||||
/* Write back an array of mailIDs */
|
||||
responseBlock["mailIDs"] = parseJSON(to!(string)(listFolder.getMessages()));
|
||||
JSONValue response;
|
||||
response["mailIDs"] = parseJSON(to!(string)(listFolder.getMessages()));
|
||||
responseBlock["response"] = response;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -286,8 +356,10 @@ public final class ButterflyClient : Thread
|
|||
/* Get the folder wanting to be listed */
|
||||
Folder listFolder = new Folder(mailbox, commandBlock["request"]["folderName"].str());
|
||||
|
||||
/* Write back an array of folder names */
|
||||
responseBlock["folders"] = parseJSON(to!(string)(listFolder.getFolders()));
|
||||
/* Write back an array of folder names */
|
||||
JSONValue response;
|
||||
response["folders"] = parseJSON(to!(string)(listFolder.getFolders()));
|
||||
responseBlock["response"] = response;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -316,7 +388,6 @@ public final class ButterflyClient : Thread
|
|||
status = -1;
|
||||
message = e.msg;
|
||||
}
|
||||
|
||||
catch(ErrnoException e)
|
||||
{
|
||||
/* Status=-1 :: I/O error */
|
||||
|
@ -339,15 +410,15 @@ public final class ButterflyClient : Thread
|
|||
responseBlock["status"] = statusBlock;
|
||||
|
||||
/* Write the response block to the client */
|
||||
writeln("Writing back response: "~responseBlock.toPrettyString());
|
||||
gprintln("Writing back response: "~responseBlock.toPrettyString());
|
||||
bool sendStatus = sendMessage(clientSocket, cast(byte[])toJSON(responseBlock));
|
||||
writeln(sendStatus);
|
||||
gprintln(sendStatus);
|
||||
|
||||
/* If there was an error writing the response back */
|
||||
if(!sendStatus)
|
||||
{
|
||||
/* End the session */
|
||||
writeln("Response write back failed");
|
||||
gprintln("Response write back failed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -360,12 +431,28 @@ public final class ButterflyClient : Thread
|
|||
}
|
||||
}
|
||||
|
||||
writeln("Closing session...");
|
||||
gprintln("Closing session...");
|
||||
|
||||
/* Close the socket */
|
||||
clientSocket.close();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Moves message from one folder, `srcFolder`, to another folder,
|
||||
* `dstFolder`.
|
||||
*/
|
||||
private Mail moveMail(Folder srcFolder, Mail ogMessage, Folder dstFolder)
|
||||
{
|
||||
/* Store a copy of the message in the destination folder `dstFolder` */
|
||||
Mail newMessage = storeMail(dstFolder, ogMessage.getMessage());
|
||||
|
||||
/* Delete the original message */
|
||||
ogMessage.deleteMessage();
|
||||
|
||||
return newMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a mail message in the users Mailbox
|
||||
* at in the given Folder, `folder`.
|
||||
|
@ -377,6 +464,23 @@ public final class ButterflyClient : Thread
|
|||
|
||||
return savedMail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the given mail message in the
|
||||
* provided folder with a new message.
|
||||
*/
|
||||
private Mail editMail(Mail messageOriginal, Folder storeFolder, JSONValue mailBlock)
|
||||
{
|
||||
Mail updatedMail;
|
||||
|
||||
/* Delete the old message */
|
||||
messageOriginal.deleteMessage();
|
||||
|
||||
/* Store the new message in the same folder */
|
||||
updatedMail = Mail.createMail(mailbox, storeFolder, mailBlock);
|
||||
|
||||
return updatedMail;
|
||||
}
|
||||
|
||||
private bool authenticate(string username, string password)
|
||||
{
|
||||
|
@ -490,9 +594,9 @@ public final class ButterflyClient : Thread
|
|||
* Check if the domain of this recipient is this server
|
||||
* or if it is a remote server.
|
||||
*/
|
||||
if(cmp(domain, server.domain) == 0)
|
||||
if(cmp(domain, listener.getDomain()) == 0)
|
||||
{
|
||||
writeln("Storing mail message to "~recipient~" ...");
|
||||
gprintln("Storing mail message to "~recipient~" ...");
|
||||
|
||||
/* Get the Mailbox of a given user */
|
||||
Mailbox userMailbox = new Mailbox(username);
|
||||
|
@ -503,7 +607,7 @@ public final class ButterflyClient : Thread
|
|||
/* Store the message in their Inbox folder */
|
||||
Mail.createMail(userMailbox, inboxFolder, mailBlock);
|
||||
|
||||
writeln("Stored mail message");
|
||||
gprintln("Stored mail message");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -518,7 +622,7 @@ public final class ButterflyClient : Thread
|
|||
private bool filterMailOutgoing(JSONValue* mailBlock)
|
||||
{
|
||||
/* Add the from field to the mail block */
|
||||
(*mailBlock)["from"] = mailbox.username~"@"~server.domain;
|
||||
(*mailBlock)["from"] = mailbox.username~"@"~listener.getDomain();
|
||||
|
||||
/* Add the sent time stamp */
|
||||
(*mailBlock)["sentTimestamp"] = Clock.currTime().toString();
|
||||
|
@ -559,7 +663,7 @@ public final class ButterflyClient : Thread
|
|||
/* Send the mail to each of the recipients */
|
||||
foreach(string recipient; recipients)
|
||||
{
|
||||
writeln("Sending mail message to "~recipient~" ...");
|
||||
gprintln("Sending mail message to "~recipient~" ...");
|
||||
|
||||
/* Get the mail address */
|
||||
string[] mailAddress = split(recipient, "@");
|
||||
|
@ -574,9 +678,9 @@ public final class ButterflyClient : Thread
|
|||
* Check if the domain of this recipient is this server
|
||||
* or if it is a remote server.
|
||||
*/
|
||||
if(cmp(domain, server.domain) == 0)
|
||||
if(listener.getServer().isLocalDomain(domain))
|
||||
{
|
||||
writeln("Local delivery occurring...");
|
||||
gprintln("Local delivery occurring...");
|
||||
|
||||
/* TODO: Add failed delivery here too */
|
||||
if(!Mailbox.isMailbox(username))
|
||||
|
@ -605,7 +709,7 @@ public final class ButterflyClient : Thread
|
|||
else
|
||||
{
|
||||
/* TODO: Do remote mail delivery */
|
||||
writeln("Remote delivery occurring...");
|
||||
gprintln("Remote delivery occurring...");
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -651,7 +755,7 @@ public final class ButterflyClient : Thread
|
|||
/* TODO: Get ["status"]["code"] code here an act on it */
|
||||
if(responseBlock["status"]["code"].integer() == 0)
|
||||
{
|
||||
writeln("Message delivered to user "~recipient);
|
||||
gprintln("Message delivered to user "~recipient);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -666,7 +770,7 @@ public final class ButterflyClient : Thread
|
|||
{
|
||||
/* When delivery fails */
|
||||
deliveryFailed:
|
||||
writeln("Error delivering to "~recipient);
|
||||
gprintln("Error delivering to "~recipient);
|
||||
|
||||
/* Append failed recipient to array of failed recipients */
|
||||
failedRecipients ~= recipient;
|
||||
|
@ -675,10 +779,10 @@ public final class ButterflyClient : Thread
|
|||
}
|
||||
}
|
||||
|
||||
writeln("Sent mail message to "~recipient);
|
||||
gprintln("Sent mail message to "~recipient);
|
||||
}
|
||||
|
||||
writeln("Mail delivered");
|
||||
gprintln("Mail delivered");
|
||||
|
||||
/**
|
||||
* If there are failed sends then send an error message
|
||||
|
@ -688,7 +792,7 @@ public final class ButterflyClient : Thread
|
|||
{
|
||||
/* Create the error message */
|
||||
JSONValue deliveryReport;
|
||||
JSONValue[] errorRecipients = [JSONValue(mailbox.username~"@"~server.domain)];
|
||||
JSONValue[] errorRecipients = [JSONValue(mailbox.username~"@"~listener.getDomain())];
|
||||
deliveryReport["recipients"] = errorRecipients;
|
||||
|
||||
/* TODO: Make more indepth, and have copy of the mail that was tried to be sent */
|
||||
|
@ -696,12 +800,12 @@ public final class ButterflyClient : Thread
|
|||
errorMessage ~= "\nThe message was:\n\n"~mailBlock.toPrettyString();
|
||||
deliveryReport["message"] = errorMessage;
|
||||
|
||||
writeln(deliveryReport);
|
||||
gprintln(deliveryReport);
|
||||
|
||||
/* Deliver the error message */
|
||||
sendMail(deliveryReport);
|
||||
|
||||
writeln("Mail delivery report sent: "~deliveryReport.toPrettyString());
|
||||
gprintln("Mail delivery report sent: "~deliveryReport.toPrettyString());
|
||||
}
|
||||
|
||||
|
||||
|
@ -711,6 +815,6 @@ public final class ButterflyClient : Thread
|
|||
/* Store the message in their Inbox folder */
|
||||
Mail.createMail(mailbox, sentFolder, mailBlock);
|
||||
|
||||
writeln("Saved mail message to sent folder");
|
||||
gprintln("Saved mail message to sent folder");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import std.json;
|
|||
import std.file;
|
||||
import std.stdio;
|
||||
import std.string;
|
||||
import gogga;
|
||||
|
||||
/**
|
||||
* Mailbox
|
||||
|
@ -31,13 +32,6 @@ public final class Mailbox
|
|||
{
|
||||
Mailbox newMailbox;
|
||||
|
||||
|
||||
|
||||
|
||||
/* TODO: Implement me */
|
||||
|
||||
/* TODO: Create folder for mailbox as `mailboxes/<username>` */
|
||||
|
||||
/* Create the mailbox directory */
|
||||
mkdir("mailboxes/"~username); /* TODO: Error handling */
|
||||
|
||||
|
@ -62,7 +56,11 @@ public final class Mailbox
|
|||
{
|
||||
Folder[] folders;
|
||||
|
||||
/* TODO: Implement me */
|
||||
/* Get a list of all the directories within this directory */
|
||||
foreach(DirEntry dirEntry; dirEntries("mailboxes/"~username~"/", SpanMode.shallow))
|
||||
{
|
||||
folders ~= new Folder(this, dirEntry.name());
|
||||
}
|
||||
|
||||
return folders;
|
||||
}
|
||||
|
@ -71,11 +69,10 @@ public final class Mailbox
|
|||
{
|
||||
Folder newFolder;
|
||||
|
||||
/* TODO: Implement folder creation */
|
||||
|
||||
/* Create the directory */
|
||||
mkdir("mailboxes/"~username~"/"~folderName);
|
||||
|
||||
// newFolder = new Folder(this, null, folderName);
|
||||
newFolder = new Folder(this, folderName);
|
||||
|
||||
return newFolder;
|
||||
}
|
||||
|
@ -92,10 +89,7 @@ public final class Mailbox
|
|||
mailFile.close();
|
||||
}
|
||||
|
||||
public void deleteMessage(Folder folder, string mailID)
|
||||
{
|
||||
/* TODO: Implement me */
|
||||
}
|
||||
|
||||
|
||||
public void deleteMailbox()
|
||||
{
|
||||
|
@ -141,6 +135,8 @@ public final class Folder
|
|||
*
|
||||
* The Mailbox is specified by `mailbox` and the location
|
||||
* of the folder within the mailbox by `folderPath`.
|
||||
*
|
||||
* TODO: DIsallow simply empty folder name (somewhere)
|
||||
*/
|
||||
this(Mailbox mailbox, string folderPath)
|
||||
{
|
||||
|
@ -173,7 +169,7 @@ public final class Folder
|
|||
}
|
||||
}
|
||||
|
||||
writeln("fhjdf");
|
||||
gprintln("fhjdf");
|
||||
return messages;
|
||||
}
|
||||
|
||||
|
@ -206,6 +202,29 @@ public final class Folder
|
|||
public void deleteFolder()
|
||||
{
|
||||
/* TODO: Implement me */
|
||||
|
||||
/* Get a list of all files in this folder */
|
||||
Mail[] messages = getMessages();
|
||||
|
||||
/* Delete all messages in this folder */
|
||||
foreach(Mail message; messages)
|
||||
{
|
||||
message.deleteMessage();
|
||||
}
|
||||
|
||||
/* Get a list of all folders in this folder */
|
||||
Folder[] folders = getFolders();
|
||||
|
||||
/**
|
||||
* Delete all child folders of the current
|
||||
*/
|
||||
foreach(Folder child; folders)
|
||||
{
|
||||
child.deleteFolder();
|
||||
}
|
||||
|
||||
/* Delete this folder */
|
||||
rmdir("mailboxes/"~mailbox.username~"/"~folderPath);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -215,11 +234,11 @@ public final class Folder
|
|||
{
|
||||
Folder newFolder;
|
||||
|
||||
/* TODO: Create here */
|
||||
|
||||
/* Create the folder in the filesystem */
|
||||
mkdir("mailboxes/"~mailbox.username~"/"~folderPath~"/"~folderName);
|
||||
|
||||
// newFolder = new Folder(mailbox, this, folderName);
|
||||
/* Create an instance of the newly created folder */
|
||||
newFolder = new Folder(mailbox, folderPath~"/"~folderName);
|
||||
|
||||
|
||||
return newFolder;
|
||||
|
@ -295,22 +314,34 @@ public final class Mail
|
|||
this.mailID = mailID;
|
||||
}
|
||||
|
||||
private void deleteMessage()
|
||||
/**
|
||||
* Removes the mail message from the folder
|
||||
*/
|
||||
public void deleteMessage()
|
||||
{
|
||||
/* Get the file system path to this message */
|
||||
string messageFilePath = mailbox.username~"/"~folder.folderPath~"/"~mailID;
|
||||
|
||||
/* Remove the message from the filesystem */
|
||||
remove(messageFilePath);
|
||||
}
|
||||
|
||||
private void sanityCheck()
|
||||
{
|
||||
/* TODO: Throw error if the message is somehow malformed */
|
||||
|
||||
/**
|
||||
* Check for `from` field
|
||||
* Check for `to` field
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the mail message JSON
|
||||
*/
|
||||
public JSONValue getMessage()
|
||||
{
|
||||
JSONValue messageBlock;
|
||||
|
@ -338,4 +369,9 @@ public final class Mail
|
|||
{
|
||||
return "\""~mailID~"\"";
|
||||
}
|
||||
}
|
||||
|
||||
public string getMailID()
|
||||
{
|
||||
return mailID;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
module client.sender;
|
||||
|
||||
import core.thread;
|
||||
|
||||
/**
|
||||
* The MailSender class is used to instantiate an object
|
||||
* which is used to start its own thread which will deliver
|
||||
* mail (remote-only) to the respective recipients of the mail
|
||||
* message specified. This is done such that the user need not
|
||||
* wait and hang whilst mail is being delivered
|
||||
*/
|
||||
public final class MailSender : Thread
|
||||
{
|
||||
/**
|
||||
* Constructs a new MailSender with the given
|
||||
* email to be delivered (remotely)
|
||||
*/
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
module server.listener;
|
||||
|
||||
import core.thread : Thread;
|
||||
import server.server : ButterflyServer;
|
||||
import std.json : JSONValue;
|
||||
|
||||
public abstract class ButterflyListener : Thread
|
||||
{
|
||||
private ButterflyServer server;
|
||||
private string listenerName;
|
||||
private JSONValue config;
|
||||
private string domain;
|
||||
|
||||
this(string listenerName, string domain, JSONValue config)
|
||||
{
|
||||
super(&run);
|
||||
this.listenerName = listenerName;
|
||||
this.config = config;
|
||||
this.domain = domain;
|
||||
}
|
||||
|
||||
public abstract void run();
|
||||
|
||||
public void setServer(ButterflyServer server)
|
||||
{
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public ButterflyServer getServer()
|
||||
{
|
||||
return server;
|
||||
}
|
||||
|
||||
public string getName()
|
||||
{
|
||||
return listenerName;
|
||||
}
|
||||
|
||||
public string getDomain()
|
||||
{
|
||||
return domain;
|
||||
}
|
||||
|
||||
public JSONValue getConfig()
|
||||
{
|
||||
return config;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
module server.listeners;
|
||||
|
||||
import core.thread : Thread;
|
||||
import server.listener : ButterflyListener;
|
||||
import std.socket : Socket, Address, SocketType, ProtocolType, parseAddress, AddressFamily;
|
||||
import std.json : JSONValue;
|
||||
import client.client;
|
||||
import std.conv : to;
|
||||
|
||||
|
||||
/* TODO: Enforce IPv4 on address */
|
||||
public class IPv4Listener : ButterflyListener
|
||||
{
|
||||
|
||||
private Socket serverSocket;
|
||||
|
||||
this(string name, JSONValue config)
|
||||
{
|
||||
super(name, config["domain"].str(), config);
|
||||
|
||||
Address bindAddress = parseAddress(config["address"].str(), to!(ushort)(config["port"].str()));
|
||||
|
||||
/* TODO: Throw an exception if not IPv4 */
|
||||
if(bindAddress.addressFamily() == AddressFamily.INET)
|
||||
{
|
||||
/**
|
||||
* Instantiate a new Socket for the given Address
|
||||
* `bindAddress` of which it will bind to.
|
||||
*/
|
||||
serverSocket = new Socket(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP);
|
||||
serverSocket.bind(bindAddress);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* TODO: Throw an exception if not IPv4 */
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override void run()
|
||||
{
|
||||
serverSocket.listen(1); //TODO: backlog
|
||||
|
||||
while(true)
|
||||
{
|
||||
/* Accept the queued connection */
|
||||
Socket clientConnection = serverSocket.accept();
|
||||
|
||||
|
||||
ButterflyClient client = new ButterflyClient(this, clientConnection);
|
||||
|
||||
/* Start the client handler */
|
||||
client.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: Enforce IPv6 on address */
|
||||
public class IPv6Listener : ButterflyListener
|
||||
{
|
||||
|
||||
private Socket serverSocket;
|
||||
|
||||
this(string name, JSONValue config)
|
||||
{
|
||||
super(name, config["domain"].str(), config);
|
||||
|
||||
Address bindAddress = parseAddress(config["address"].str(), to!(ushort)(config["port"].str()));
|
||||
|
||||
/* TODO: Throw an exception if not IPv6 */
|
||||
if(bindAddress.addressFamily() == AddressFamily.INET6)
|
||||
{
|
||||
/**
|
||||
* Instantiate a new Socket for the given Address
|
||||
* `bindAddress` of which it will bind to.
|
||||
*/
|
||||
serverSocket = new Socket(AddressFamily.INET6, SocketType.STREAM, ProtocolType.TCP);
|
||||
serverSocket.bind(bindAddress);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* TODO: Throw an exception if not IPv6 */
|
||||
}
|
||||
}
|
||||
|
||||
public override void run()
|
||||
{
|
||||
serverSocket.listen(1); //TODO: backlog
|
||||
|
||||
while(true)
|
||||
{
|
||||
/* Accept the queued connection */
|
||||
Socket clientConnection = serverSocket.accept();
|
||||
|
||||
|
||||
ButterflyClient client = new ButterflyClient(this, clientConnection);
|
||||
|
||||
/* Start the client handler */
|
||||
client.start();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,10 @@ module server.server;
|
|||
import std.socket : Socket, Address, SocketType, ProtocolType;
|
||||
import client.client : ButterflyClient;
|
||||
import std.file : mkdir, exists, isDir;
|
||||
import server.listener : ButterflyListener;
|
||||
import std.stdio : writeln;
|
||||
import std.string : cmp;
|
||||
import gogga;
|
||||
|
||||
public final class ButterflyServer
|
||||
{
|
||||
|
@ -10,6 +14,7 @@ public final class ButterflyServer
|
|||
* TODO: Later implement listeners so that we can
|
||||
* bind to multiple sockets.
|
||||
*/
|
||||
private ButterflyListener[] listeners;
|
||||
|
||||
/**
|
||||
* Socket to listen for incoming connections on
|
||||
|
@ -21,10 +26,7 @@ public final class ButterflyServer
|
|||
*/
|
||||
private bool active = true;
|
||||
|
||||
/* TODO: Server domain */
|
||||
public string domain;
|
||||
|
||||
this(Address bindAddress, string domain)
|
||||
this(ButterflyListener[] listeners)
|
||||
{
|
||||
/**
|
||||
* Create the needed directories (if not already present)
|
||||
|
@ -32,20 +34,17 @@ public final class ButterflyServer
|
|||
directoryCheck();
|
||||
|
||||
/**
|
||||
* Instantiate a new Socket for the given Address
|
||||
* `bindAddress` of which it will bind to.
|
||||
* Set all the listeners
|
||||
*/
|
||||
serverSocket = new Socket(bindAddress.addressFamily, SocketType.STREAM, ProtocolType.TCP);
|
||||
serverSocket.bind(bindAddress);
|
||||
this.listeners = listeners;
|
||||
|
||||
/* Set the domain of the server */
|
||||
this.domain = domain;
|
||||
|
||||
/* Start accepting connections */
|
||||
run();
|
||||
|
||||
/* Close the socket */
|
||||
serverSocket.close();
|
||||
/**
|
||||
* Set the server of all listeners to this server
|
||||
*/
|
||||
foreach(ButterflyListener listener; listeners)
|
||||
{
|
||||
listener.setServer(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void directoryCheck()
|
||||
|
@ -91,28 +90,33 @@ public final class ButterflyServer
|
|||
}
|
||||
}
|
||||
|
||||
private void run()
|
||||
public void run()
|
||||
{
|
||||
/* TODO: backlog */
|
||||
/* Start accepting incoming connections */
|
||||
serverSocket.listen(1);
|
||||
|
||||
/* TODO: Loop here */
|
||||
while(active)
|
||||
/* Start the listeners */
|
||||
foreach(ButterflyListener listener; listeners)
|
||||
{
|
||||
/* Block for an incoming queued connection */
|
||||
Socket clientSocket = serverSocket.accept();
|
||||
|
||||
/**
|
||||
* Create a new ButterflyClient to represent the
|
||||
* client connection.
|
||||
*/
|
||||
ButterflyClient client = new ButterflyClient(this, clientSocket);
|
||||
|
||||
/* Start the client thread */
|
||||
client.start();
|
||||
|
||||
/* TODO: Add to array */
|
||||
gprintln("Starting listener \"" ~ listener.getName() ~"\" ...");
|
||||
gprintln("Listener is using configuration: "~listener.getConfig().toPrettyString());
|
||||
listener.start();
|
||||
gprintln("Listener \"" ~ listener.getName() ~ "\" started");
|
||||
}
|
||||
}
|
||||
|
||||
public bool isLocalDomain(string domain)
|
||||
{
|
||||
/**
|
||||
* Loop through each listener and check if the requested domain
|
||||
* appears in one of them.
|
||||
*/
|
||||
foreach(ButterflyListener listener; listeners)
|
||||
{
|
||||
if(cmp(listener.getDomain(), domain) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"listeners" : {
|
||||
"enabled" : ["listener1"],
|
||||
"listener1" : {
|
||||
"type" : "ipv4",
|
||||
"domain" : "10.0.0.8:2222",
|
||||
"address" : "0.0.0.0",
|
||||
"port" : "6969"
|
||||
},
|
||||
"listener2" : {
|
||||
"type" : "ipv6",
|
||||
"domain" : "10.0.0.8:2222",
|
||||
"address" : "::",
|
||||
"port" : "6969"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"listeners" : {
|
||||
"enabled" : ["listener1"],
|
||||
"listener1" : {
|
||||
"type" : "ipv4",
|
||||
"domain" : "10.1.0.4:2222",
|
||||
"address" : "0.0.0.0",
|
||||
"port" : "6969"
|
||||
},
|
||||
"listener2" : {
|
||||
"type" : "ipv6",
|
||||
"domain" : "10.1.0.4:2222",
|
||||
"address" : "::",
|
||||
"port" : "6969"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
Configuring Butterfly
|
||||
=====================
|
||||
|
||||
In order to configure Butterfly you will want to create a file named `butterflyd.json` in the same directory as your
|
||||
`butterflyd` executable. The contents should look something like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"listeners" : {
|
||||
"enabled" : ["listener1"],
|
||||
"listener1" : {
|
||||
"type" : "ipv4",
|
||||
"domain" : "10.1.0.4:6969",
|
||||
"address" : "0.0.0.0",
|
||||
"port" : "6969"
|
||||
},
|
||||
"listener2" : {
|
||||
"type" : "ipv6",
|
||||
"domain" : "10.0.0.9:2222",
|
||||
"address" : "::",
|
||||
"port" : "6969"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The first section if the `"listeners"` section. This key contains an array, `"enabled"`, which contains a list of strings which name which listeners, specified in the same JSON object, should be enabled when the daemon starts up. Listeners are basically TCP port associations with
|
||||
a few other settings that say the server should listen on this port and this IP address. If you take a look at the first listener (which also happens to be the only enabled one), `"listener1"`, you will see that is contains a `"type"` field. This spcifies whether or not the listener is for IPv4 or IPv6. The `"port"` and `"address"` fields specify the port and address to bind to.
|
||||
|
||||
Lastly the domain should be the publicly facing address/domain and port pair for your mail server. It is used when generating `from` field in outgoing mail as this isn't done on the client side but rather the server side. You want this to be a reachable address and port pairing
|
||||
as replying to such an email recieved from such a domain should be possible.
|
Loading…
Reference in New Issue