Compare commits

...

50 Commits

Author SHA1 Message Date
Tristan B. Kildaire 5a9896613c Added documentation on setting it up 2021-01-23 17:29:11 +02:00
Tristan B. Kildaire 98c6208fe7 Fixed missing import 2021-01-23 12:34:30 +02:00
Tristan B. Kildaire 89db434578 Enabled gogga printlns in server.d 2021-01-23 12:34:10 +02:00
Tristan B. Kildaire b4501b5da1 Enabled gogga printlns in client.d 2021-01-23 12:33:58 +02:00
Tristan B. Kildaire 16aa7ac450 Enabled gogga printlns in mail.d 2021-01-23 12:32:47 +02:00
Tristan B. Kildaire 2fb72f253b Enabled gogga printlns in server.d 2021-01-23 12:32:01 +02:00
Tristan B. Kildaire 17c6e5ce08 Replaced old printlns with gogga printlns in app.d 2021-01-23 12:31:35 +02:00
Tristan B. Kildaire a3d240cb83 Added gogga 2021-01-23 12:30:54 +02:00
Tristan B. Kildaire 436fe3b803 Updated testing ports 2021-01-21 17:44:54 +02:00
Tristan B. Kildaire c044ec5b31 Code cleanup 2021-01-21 17:44:45 +02:00
Tristan B. Kildaire 4ab48120fa Updated dependancies 2021-01-21 16:14:53 +02:00
Tristan B. Kildaire a9c2e0680f Updated configuration for testing 2021-01-21 16:14:30 +02:00
Tristan B. Kildaire 75faa5d564 WIP: Testing configurations for server-to-server tests 2021-01-20 18:44:53 +02:00
Tristan B. Kildaire 25961601bc Upgraded to new bformat 2021-01-20 18:15:43 +02:00
Tristan B. Kildaire cc832f151b Updated 2021-01-20 18:12:44 +02:00
Tristan B. Kildaire 755d86cf1e Added tristanable 2021-01-20 18:12:20 +02:00
Tristan B. Kildaire 2553978839 Pushed some shut 2021-01-20 18:11:03 +02:00
Tristan B. Kildaire 738eb9397b API update for 'listFolder' and 'listMail' 2020-07-29 12:21:17 +02:00
Tristan B. Kildaire 65a5ece0c0 Added response for 'moveMail' 2020-07-29 12:18:50 +02:00
Tristan B. Kildaire d8b32368e7 Fixed bug whereby JSONValue object did not exist causing failure, for 'storeMail' 2020-07-29 11:00:52 +02:00
Tristan B. Kildaire fbfc45ec09 Fixed bug whereby JSONValue object did not exist causing failure, for 'storeMail' 2020-07-29 10:57:05 +02:00
Tristan B. Kildaire 0bf0af860a Fixed bug whereby JSONValue object did not exist causing failure, for 'storeMail' 2020-07-29 10:54:19 +02:00
Tristan B. Kildaire d912721723 'addBaseFolder' fully implemented 2020-07-29 10:22:21 +02:00
Tristan B. Kildaire b55921fea6 Implemented 'createFolder' 2020-07-29 10:19:10 +02:00
Tristan B. Kildaire 0392752b01 Implemented 'createFolder' 2020-07-29 10:16:47 +02:00
Tristan B. Kildaire ae944f5647 Fixed 'deleteFolder' crash 2020-07-29 10:16:00 +02:00
Tristan B. Kildaire e31a6f09b8 Removed unused function 2020-07-29 10:05:06 +02:00
Tristan B. Kildaire d091800ce7 Implemented mail moving 2020-07-28 09:11:48 +02:00
Tristan B. Kildaire 5d12378cd3 Implemented folder deletion in the protocol 2020-07-27 21:54:34 +02:00
Tristan B. Kildaire 704200025c Implemented 'deleteFolder' 2020-07-27 21:42:45 +02:00
Tristan B. Kildaire 478cafd229 Implemented 'deleteMessage' 2020-07-27 21:10:23 +02:00
Tristan B. Kildaire 1e8cd9117d Return mailID of stored mail 2020-07-27 17:02:03 +02:00
Tristan B. Kildaire ed2694bf35 Now runs 2020-07-27 15:07:23 +02:00
Tristan B. Kildaire e9a5af7863 Fixed all compilation bugs. 2020-07-27 14:57:09 +02:00
Tristan B. Kildaire 785113e5f8 Fixed all compile errors within client.d 2020-07-27 14:56:36 +02:00
Tristan B. Kildaire 1d50c30e29 Implemented `isLocalDomain` function to check if the domain of a recipient is that of this mail server 2020-07-27 14:18:03 +02:00
Tristan B. Kildaire 5ea1ac2ba5 Fixed issue with order of construction 2020-07-27 14:14:30 +02:00
Tristan B. Kildaire c23214b186 Order of construction details (WIP) 2020-07-27 14:12:56 +02:00
Tristan B. Kildaire c7398586ea Fixed compile errors. 2020-07-27 14:11:07 +02:00
Tristan B. Kildaire e49f8fa9fd Added listener construction function (WIP) 2020-07-27 13:56:55 +02:00
Tristan B. Kildaire 0326b61e34 Added listener architecture 2020-07-27 13:42:09 +02:00
Tristan B. Kildaire ad5ee5a9ae Fix runtime JSON error 2020-07-22 13:11:21 +02:00
Tristan B. Kildaire cea2a007b2 Minimized imports 2020-07-22 13:04:24 +02:00
Tristan B. Kildaire 4339075c8a Changed port from JSON string to JSON integer 2020-07-22 13:02:49 +02:00
Tristan B. Kildaire 4692bcaa36 Added port to configuration 2020-07-22 13:00:01 +02:00
Tristan B. Kildaire f1756e250a Updated debug prints 2020-07-20 23:46:41 +02:00
Tristan B. Kildaire ded976f709 Upddated error messages 2020-07-20 23:29:04 +02:00
Tristan B. Kildaire 2f204f0e83 Fixed error report sending code placement. 2020-07-20 20:41:48 +02:00
Tristan B. Kildaire 1c43e05e70 Fixed JSON type error 2020-07-20 20:38:48 +02:00
Tristan B. Kildaire 7a3ff57364 Report print out for testing 2020-07-20 20:37:23 +02:00
13 changed files with 559 additions and 122 deletions

View File

@ -1,4 +1,17 @@
{
"domain" : "10.0.0.9:2222",
"address" : "0.0.0.0"
"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"
}
}
}

View File

@ -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",

View File

@ -1,6 +1,8 @@
{
"fileVersion": 1,
"versions": {
"bformat": "1.0.8"
"bformat": "3.1.0",
"gogga": "0.0.2",
"tristanable": "2.2.0"
}
}

View File

@ -3,11 +3,16 @@ import std.stdio;
import server.server : ButterflyServer;
import std.socket : Address, parseAddress;
import std.file;
import std.json;
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;
@ -20,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(), 6969);
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;
}

View File

@ -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 server "~domain);
gprintln("Message delivered to user "~recipient);
}
else
{
@ -666,7 +770,7 @@ public final class ButterflyClient : Thread
{
/* When delivery fails */
deliveryFailed:
writeln("Error delivering to server "~domain);
gprintln("Error delivering to "~recipient);
/* Append failed recipient to array of failed recipients */
failedRecipients ~= recipient;
@ -675,32 +779,35 @@ public final class ButterflyClient : Thread
}
}
/**
* If there are failed sends then send an error message
* to the sender.
*/
if(failedRecipients.length)
{
/* Create the error message */
JSONValue deliveryReport;
JSONValue[] errorRecipients = [JSONValue("")];
deliveryReport["recipients"] = mailbox.username~"@"~server.domain;
/* TODO: Make more indepth, and have copy of the mail that was tried to be sent */
string errorMessage = "There was an error delivery the mail to: "~to!(string)(recipients)~"\n";
errorMessage ~= "\nThe message was:\n\n"~mailBlock.toPrettyString();
deliveryReport["message"] = errorMessage;
/* Deliver the error message */
sendMail(deliveryReport);
writeln("Mail delivery report sent: "~deliveryReport.toPrettyString());
}
writeln("Sent mail message");
gprintln("Sent mail message to "~recipient);
}
gprintln("Mail delivered");
/**
* If there are failed sends then send an error message
* to the sender.
*/
if(failedRecipients.length)
{
/* Create the error message */
JSONValue deliveryReport;
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 */
string errorMessage = "There was an error delivery the mail to: "~to!(string)(recipients)~"\n";
errorMessage ~= "\nThe message was:\n\n"~mailBlock.toPrettyString();
deliveryReport["message"] = errorMessage;
gprintln(deliveryReport);
/* Deliver the error message */
sendMail(deliveryReport);
gprintln("Mail delivery report sent: "~deliveryReport.toPrettyString());
}
writeln("Mail delivered");
/* Store the message in this user's "Sent" folder */
Folder sentFolder = new Folder(mailbox, "Sent");
@ -708,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");
}
}
}

View File

@ -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;
}
}

18
source/client/sender.d Normal file
View File

@ -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)
*/
}

49
source/server/listener.d Normal file
View File

@ -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;
}
}

102
source/server/listeners.d Normal file
View File

@ -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();
}
}
}

View File

@ -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;
}
}

17
testing/b1.json Normal file
View File

@ -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"
}
}
}

17
testing/b2.json Normal file
View File

@ -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"
}
}
}

31
tutorial.md Normal file
View File

@ -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.