Folder structure reconstruction
This commit is contained in:
116
MailServer/SMTPServer/MTACommands.cs
Normal file
116
MailServer/SMTPServer/MTACommands.cs
Normal file
@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace MailServer.SMTPServer
|
||||
{
|
||||
public class MTACommandsDict : Dictionary<MTACommands, String>
|
||||
{
|
||||
public MTACommandsDict()
|
||||
{
|
||||
Add(MTACommands.HELO, "HELO");
|
||||
Add(MTACommands.DATA, "DATA");
|
||||
Add(MTACommands.EXPN, "EXPN");
|
||||
Add(MTACommands.HELP, "HELP");
|
||||
Add(MTACommands.MAIL_FROM, "MAIL FROM");
|
||||
Add(MTACommands.NOOP, "NOOP");
|
||||
Add(MTACommands.QUIT, "QUIT");
|
||||
Add(MTACommands.RCPT_TO, "RCPT TO");
|
||||
Add(MTACommands.RSET, "RSET");
|
||||
Add(MTACommands.SAML_FROM, "SAML FROM");
|
||||
Add(MTACommands.SEND_FROM, "SEND FROM");
|
||||
Add(MTACommands.SOML_FROM, "SOML FROM");
|
||||
Add(MTACommands.TURN, "TURN");
|
||||
Add(MTACommands.VERB, "VERB");
|
||||
Add(MTACommands.VRFY, "VRFY");
|
||||
Add(MTACommands.ATRN, "ATRN");
|
||||
Add(MTACommands.AUTH, "AUTH");
|
||||
Add(MTACommands.BDAT, "BDAT");
|
||||
Add(MTACommands.EHLO, "EHLO");
|
||||
Add(MTACommands.ETRN, "ETRN");
|
||||
Add(MTACommands.RCPT, "RCPT");
|
||||
Add(MTACommands.SAML, "SAML");
|
||||
Add(MTACommands.SEND, "SEND");
|
||||
Add(MTACommands.SOML, "SOML");
|
||||
Add(MTACommands.STARTTL, "STARTTLs");
|
||||
Add(MTACommands.AUTH_LOGIN, "AUTH LOGIN");
|
||||
}
|
||||
}
|
||||
|
||||
public enum MTACommands
|
||||
{
|
||||
HELO,
|
||||
MAIL_FROM,
|
||||
RCPT_TO,
|
||||
DATA,
|
||||
RSET,
|
||||
QUIT,
|
||||
HELP,
|
||||
VRFY,
|
||||
EXPN,
|
||||
VERB,
|
||||
NOOP,
|
||||
TURN,
|
||||
SEND_FROM,
|
||||
SOML_FROM,
|
||||
SAML_FROM,
|
||||
ATRN,
|
||||
AUTH,
|
||||
BDAT,
|
||||
EHLO,
|
||||
ETRN,
|
||||
RCPT,
|
||||
SAML,
|
||||
SEND,
|
||||
SOML,
|
||||
STARTTL,
|
||||
AUTH_LOGIN
|
||||
}
|
||||
|
||||
public enum Extensions
|
||||
{
|
||||
PIPELINING,
|
||||
SIZE,
|
||||
STARTTLS,
|
||||
}
|
||||
|
||||
public enum ResponseCodes
|
||||
{
|
||||
C211 = 211,
|
||||
C214 = 214,
|
||||
C220 = 220,
|
||||
C221 = 221,
|
||||
C250 = 250,
|
||||
C251 = 251,
|
||||
C252 = 252,
|
||||
C253 = 253,
|
||||
C334 = 334,
|
||||
C354 = 354,
|
||||
C355 = 355,
|
||||
C421 = 421,
|
||||
C432 = 432,
|
||||
C450 = 450,
|
||||
C451 = 451,
|
||||
C452 = 452,
|
||||
C453 = 453,
|
||||
C454 = 454,
|
||||
C458 = 458,
|
||||
C459 = 459,
|
||||
C500 = 500,
|
||||
C501 = 501,
|
||||
C502 = 502,
|
||||
C503 = 503,
|
||||
C504 = 504,
|
||||
C521 = 521,
|
||||
C530 = 530,
|
||||
C534 = 534,
|
||||
C538 = 538,
|
||||
C550 = 550,
|
||||
C551 = 551,
|
||||
C552 = 552,
|
||||
C553 = 553,
|
||||
C554 = 554
|
||||
}
|
||||
}
|
45
MailServer/SMTPServer/SmtpCommand.cs
Normal file
45
MailServer/SMTPServer/SmtpCommand.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MailServer.SMTPServer
|
||||
{
|
||||
public class SmtpCommand
|
||||
{
|
||||
public string Command { get; set; }
|
||||
public string[] Parameters { get; set; }
|
||||
|
||||
public SmtpCommand(string command)
|
||||
{
|
||||
Parameters = new string[0];
|
||||
if (command.Length == 0 || command.Equals(""))
|
||||
{
|
||||
Command = "FREELINE";
|
||||
return;
|
||||
}
|
||||
else if (command.Equals("."))
|
||||
{
|
||||
Command = "JUSTADOT";
|
||||
return;
|
||||
}
|
||||
# if DEBUG
|
||||
else if (command.Equals("<CRLF>.<CRLF>"))
|
||||
{
|
||||
Command = "DATAEND";
|
||||
return;
|
||||
}
|
||||
# endif
|
||||
|
||||
var sp = command.Split(' ');
|
||||
|
||||
Command = sp[0];
|
||||
|
||||
if (sp.Length < 2) return;
|
||||
Parameters = new string[sp.Length - 1];
|
||||
for (int i = 1; i < sp.Length; i++)
|
||||
{
|
||||
Parameters[i - 1] = sp[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
38
MailServer/SMTPServer/SmtpPortListener.cs
Normal file
38
MailServer/SMTPServer/SmtpPortListener.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace MailServer.SMTPServer
|
||||
{
|
||||
public class SmtpPortListener
|
||||
{
|
||||
private static List<int> Ports { get; set; }
|
||||
private int Port { get; set; }
|
||||
private TcpListener Listener;
|
||||
|
||||
public SmtpPortListener(int port)
|
||||
{
|
||||
Port = port;
|
||||
Logging.AddLogMessage(Logging.LoggingType.INFO, "Start TCP listener on port: " + Port);
|
||||
Thread thread = new Thread(StartListeningAsync);
|
||||
thread.Start();
|
||||
}
|
||||
|
||||
public async void StartListeningAsync()
|
||||
{
|
||||
Listener = new TcpListener(IPAddress.Any, Port);
|
||||
Listener.Start();
|
||||
|
||||
while (true)
|
||||
{
|
||||
var client = await Listener.AcceptTcpClientAsync();
|
||||
var ip = (IPEndPoint)client.Client.RemoteEndPoint;
|
||||
Logging.AddLogMessage(Logging.LoggingType.INFO, "New Client Logged in with ip: " + ip.Address.ToString() + "On port: " + Port + " to port: " + ip.Port);
|
||||
new SmtpServerSession(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
319
MailServer/SMTPServer/SmtpServerSession.cs
Normal file
319
MailServer/SMTPServer/SmtpServerSession.cs
Normal file
@ -0,0 +1,319 @@
|
||||
using SMTPServer;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
|
||||
namespace MailServer.SMTPServer
|
||||
{
|
||||
class SmtpServerSession : SmtpSession
|
||||
{
|
||||
private bool Initialized { get; set; }
|
||||
private string SenderHostname { get; set; }
|
||||
|
||||
private bool MailFromSet { get; set; }
|
||||
private string MailFrom { get; set; }
|
||||
|
||||
private List<string> Recipients { get; set; }
|
||||
|
||||
private bool WaitForData { get; set; }
|
||||
private string Data { get; set; }
|
||||
private bool FreeLine { get; set; }
|
||||
|
||||
private bool AuthActive { get; set; }
|
||||
private string Username { get; set; }
|
||||
private string Password { get; set; }
|
||||
private bool Authenticated { get; set; }
|
||||
|
||||
public SmtpServerSession(TcpClient client) : base(client) {
|
||||
Recipients = new List<string>();
|
||||
SendResponse(ResponseCodes.C220, Configuration.Hostname + " Welcome to SimpleMail DotNetCore based AIO MailServer");
|
||||
NewLine += SmtpServerSession_NewLine;
|
||||
}
|
||||
|
||||
private void SmtpServerSession_NewLine(string line)
|
||||
{
|
||||
try
|
||||
{
|
||||
var command = new SmtpCommand(line);
|
||||
|
||||
if (WaitForData)
|
||||
{
|
||||
#if DEBUG
|
||||
bool dataend = false;
|
||||
if (command.Command.Equals("DATAEND"))
|
||||
{
|
||||
dataend = true;
|
||||
}
|
||||
#endif
|
||||
FreeLine = true;
|
||||
if (!FreeLine && command.Command.Equals("FREELINE"))
|
||||
{
|
||||
FreeLine = true;
|
||||
return;
|
||||
}
|
||||
else if ((FreeLine && command.Command.Equals("JUSTADOT")) || dataend)
|
||||
{
|
||||
SendResponse(ResponseCodes.C250, "OK");
|
||||
foreach (var r in Recipients)
|
||||
{
|
||||
var split = r.Split('@');
|
||||
var name = split[0];
|
||||
var domain = split[1];
|
||||
|
||||
using (var context = new MysqlDB())
|
||||
{
|
||||
context.Database.EnsureCreated();
|
||||
int accountId = 0;
|
||||
|
||||
var dmn = context.Domains.Where(x => x.Domain.Equals(domain));
|
||||
var domainidx = dmn.FirstOrDefault().Id;
|
||||
|
||||
var res = context.Accounts.Where(x => x.Domain.Equals(domainidx) && x.Name.Equals(name));
|
||||
if (res.Count() < 1)
|
||||
{
|
||||
var res2 = context.Aliases.Where(x => x.SourceName.Equals(name) && x.SourceDomain.Equals(domain));
|
||||
if (res2.Count() < 1)
|
||||
{
|
||||
var res3 = context.Accounts.Where(x => x.Domain.Equals(domain) && x.CatchAll == true);
|
||||
if (res3.Count() < 1)
|
||||
{
|
||||
accountId = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
accountId = res3.FirstOrDefault().Id;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
accountId = res2.FirstOrDefault().DestinationAccount;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
accountId = res.FirstOrDefault().Id;
|
||||
}
|
||||
int folderid = 0;
|
||||
|
||||
var res4 = context.Folders.Where(x => x.AccountId == accountId && x.StandardFolder == (int)StandardFolders.INBOX);
|
||||
if (res4.Count() < 1)
|
||||
{
|
||||
//ToDo ERROR
|
||||
folderid = 0;
|
||||
}
|
||||
else folderid = res4.FirstOrDefault().Id;
|
||||
|
||||
var nm = new Mails()
|
||||
{
|
||||
AccountId = accountId,
|
||||
ReceiveDate = DateTime.Now,
|
||||
Data = Data,
|
||||
SendedByServerIp = IPEndPoint.Address.ToString(),
|
||||
From = MailFrom,
|
||||
To = r,
|
||||
Folder = folderid
|
||||
};
|
||||
context.Mails.Add(nm);
|
||||
context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
MailFromSet = false;
|
||||
Recipients.Clear();
|
||||
WaitForData = false;
|
||||
return;
|
||||
}
|
||||
SendResponse(ResponseCodes.C250, "OK");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
Data += command.Command;
|
||||
|
||||
foreach (var s in command.Parameters)
|
||||
{
|
||||
Data += " " + s;
|
||||
}
|
||||
|
||||
Data += "\r\n";
|
||||
FreeLine = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (command.Command.ToUpper())
|
||||
{
|
||||
case "EHLO":
|
||||
if (command.Parameters.Length < 1)
|
||||
{
|
||||
SendResponse(ResponseCodes.C501, "Hostname Required");
|
||||
return;
|
||||
}
|
||||
SenderHostname = command.Parameters[0];
|
||||
Initialized = true;
|
||||
SendExtensionResponse(Extensions.PIPELINING);
|
||||
|
||||
if (Configuration.STARTTLS_Active)
|
||||
{
|
||||
#pragma warning disable CS0162 // Unerreichbarer Code wurde entdeckt.
|
||||
SendExtensionResponse(Extensions.STARTTLS);
|
||||
#pragma warning restore CS0162 // Unerreichbarer Code wurde entdeckt.
|
||||
}
|
||||
|
||||
SendResponse(ResponseCodes.C250, "SIZE " + (Configuration.MaxMessageSizeInKb * 1000).ToString());
|
||||
return;
|
||||
case "HELO":
|
||||
if (command.Parameters.Length < 1)
|
||||
{
|
||||
SendResponse(ResponseCodes.C501, "Hostname Required");
|
||||
return;
|
||||
}
|
||||
SenderHostname = command.Parameters[0];
|
||||
Initialized = true;
|
||||
SendResponse(ResponseCodes.C250, "OK");
|
||||
return;
|
||||
case "RSET":
|
||||
//ToDo
|
||||
return;
|
||||
case "QUIT":
|
||||
SendResponse(ResponseCodes.C221, Configuration.Hostname + " closing transmission channel");
|
||||
CloseAll();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Initialized)
|
||||
{
|
||||
switch (command.Command.ToUpper())
|
||||
{
|
||||
case "MAIL":
|
||||
if (command.Parameters.Length < 1)
|
||||
{
|
||||
SendResponse(ResponseCodes.C501);
|
||||
return;
|
||||
}
|
||||
if (!command.Parameters[0].ToUpper().StartsWith("FROM"))
|
||||
{
|
||||
SendResponse(ResponseCodes.C501);
|
||||
return;
|
||||
}
|
||||
if (command.Parameters.Length == 1)
|
||||
{
|
||||
var m = command.Parameters[0].Split(':');
|
||||
if (m.Length != 2)
|
||||
{
|
||||
SendResponse(ResponseCodes.C501);
|
||||
return;
|
||||
}
|
||||
MailFrom = m[1];
|
||||
}
|
||||
else if (command.Parameters.Length == 2)
|
||||
{
|
||||
MailFrom = command.Parameters[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
SendResponse(ResponseCodes.C501);
|
||||
return;
|
||||
}
|
||||
|
||||
MailFrom.Replace("<", String.Empty);
|
||||
MailFrom.Replace(">", String.Empty);
|
||||
MailFromSet = true;
|
||||
SendResponse(ResponseCodes.C250, "OK");
|
||||
return;
|
||||
}
|
||||
|
||||
if (MailFromSet)
|
||||
{
|
||||
switch (command.Command.ToUpper())
|
||||
{
|
||||
case "RCPT":
|
||||
if (command.Parameters.Length < 1)
|
||||
{
|
||||
SendResponse(ResponseCodes.C501);
|
||||
return;
|
||||
}
|
||||
if (!command.Parameters[0].ToUpper().StartsWith("TO"))
|
||||
{
|
||||
SendResponse(ResponseCodes.C501);
|
||||
return;
|
||||
}
|
||||
|
||||
var rcpt = "";
|
||||
if (command.Parameters.Length == 1)
|
||||
{
|
||||
var m = command.Parameters[0].Split(':');
|
||||
if (m.Length != 2)
|
||||
{
|
||||
SendResponse(ResponseCodes.C501);
|
||||
return;
|
||||
}
|
||||
rcpt = m[1];
|
||||
}
|
||||
else if (command.Parameters.Length == 2)
|
||||
{
|
||||
rcpt = command.Parameters[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
SendResponse(ResponseCodes.C501);
|
||||
return;
|
||||
}
|
||||
|
||||
rcpt = rcpt.Replace("<", String.Empty);
|
||||
rcpt = rcpt.Replace(">", String.Empty);
|
||||
|
||||
var split = rcpt.Split('@');
|
||||
if (split.Length != 2)
|
||||
{
|
||||
SendResponse(ResponseCodes.C501);
|
||||
return;
|
||||
}
|
||||
|
||||
using (var context = new MysqlDB())
|
||||
{
|
||||
context.Database.EnsureCreated();
|
||||
var res = context.Domains.Where(x => x.Domain.Equals(split[1]));
|
||||
if (res.Count() < 1)
|
||||
{
|
||||
SendResponse(ResponseCodes.C550, "No such user here");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Recipients.Add(rcpt);
|
||||
SendResponse(ResponseCodes.C250, "OK");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Recipients.Count > 0)
|
||||
{
|
||||
switch (command.Command.ToUpper())
|
||||
{
|
||||
case "DATA":
|
||||
SendResponse(ResponseCodes.C354, "Start mail input, end with <CRLF>.<CRLF>");
|
||||
WaitForData = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
SendResponse(ResponseCodes.C500, command.Command);
|
||||
//throw new NotImplementedException();
|
||||
}
|
||||
} catch (Exception e)
|
||||
{
|
||||
Logging.AddException(e);
|
||||
SendResponse(ResponseCodes.C554, "internal error");
|
||||
SendResponse(ResponseCodes.C221, "Closing Transmission");
|
||||
CloseAll();
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearFields()
|
||||
{
|
||||
//ToDo einbauen
|
||||
}
|
||||
|
||||
}
|
||||
}
|
116
MailServer/SMTPServer/SmtpSession.cs
Normal file
116
MailServer/SMTPServer/SmtpSession.cs
Normal file
@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace MailServer.SMTPServer
|
||||
{
|
||||
public delegate void ReceivedLineEventHandler(string line);
|
||||
|
||||
class SmtpSession
|
||||
{
|
||||
public event ReceivedLineEventHandler NewLine;
|
||||
|
||||
private TcpClient Client { get; set; }
|
||||
public IPEndPoint IPEndPoint { get; private set; }
|
||||
private NetworkStream Stream { get; set; }
|
||||
private Encoding Encoding = Encoding.ASCII;
|
||||
private string Others;
|
||||
private Thread Worker;
|
||||
private bool Active = true;
|
||||
private int SessionID { get; set; }
|
||||
|
||||
public SmtpSession(TcpClient client)
|
||||
{
|
||||
Client = client;
|
||||
IPEndPoint = (IPEndPoint)client.Client.RemoteEndPoint;
|
||||
Stream = client.GetStream();
|
||||
Others = "";
|
||||
SessionID = new Random(123).Next(10000, 99999);
|
||||
Logging.AddLogMessage(Logging.LoggingType.INFO, "New SMTPSession with id '" + SessionID + "' started");
|
||||
Worker = new Thread(ReadClientInput);
|
||||
Worker.Start();
|
||||
}
|
||||
|
||||
public void SendExtensionResponse(Extensions extension) => SendResponse("250-" + extension.ToString());
|
||||
|
||||
public void SendResponse(ResponseCodes responseCode) => SendResponse(responseCode, "");
|
||||
|
||||
public void SendResponse(ResponseCodes responseCode, string args) => SendResponse(((int)responseCode).ToString() + " " + args);
|
||||
|
||||
|
||||
public void SendResponse(string response)
|
||||
{
|
||||
Logging.AddLogMessage(Logging.LoggingType.INFO, response);
|
||||
response += '\r' + '\n';
|
||||
var bytes = Encoding.UTF8.GetBytes(response);
|
||||
Stream.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
private void ReadClientInput()
|
||||
{
|
||||
while (Active)
|
||||
{
|
||||
if (Stream.DataAvailable)
|
||||
{
|
||||
byte[] buffer = new byte[Client.ReceiveBufferSize];
|
||||
var readed = Stream.Read(buffer, 0, buffer.Length);
|
||||
|
||||
var str = Encoding.GetString(buffer, 0, readed);
|
||||
lock (Others)
|
||||
{
|
||||
Others += str;
|
||||
}
|
||||
|
||||
CheckLines();
|
||||
}
|
||||
else
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private bool r = false;
|
||||
|
||||
private void CheckLines()
|
||||
{
|
||||
lock (Others)
|
||||
{
|
||||
var line = "";
|
||||
foreach (char c in Others)
|
||||
{
|
||||
if (c.Equals('\r'))
|
||||
{
|
||||
r = true;
|
||||
}
|
||||
else if (r && c.Equals('\n'))
|
||||
{
|
||||
//Console.WriteLine(line);
|
||||
Logging.AddLogMessage(Logging.LoggingType.DEBUG, "Seession " + SessionID + ": received line: " + line);
|
||||
NewLine(line);
|
||||
line = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
line += c;
|
||||
r = false;
|
||||
}
|
||||
}
|
||||
Others = line;
|
||||
}
|
||||
}
|
||||
|
||||
public void CloseAll()
|
||||
{
|
||||
Stream.Dispose();
|
||||
Client.Dispose();
|
||||
Active = false;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user