508 lines
18 KiB
Plaintext
508 lines
18 KiB
Plaintext
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Microsoft Research Singularity
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// Note: SMTP Server.
|
|
//
|
|
using Microsoft.Singularity.Applications;
|
|
using Microsoft.Singularity.Channels;
|
|
using Microsoft.Singularity.Diagnostics.Contracts;
|
|
using Microsoft.Singularity.Endpoint;
|
|
using Microsoft.Singularity.Directory;
|
|
using Microsoft.Singularity.V1.Services;
|
|
using Microsoft.Singularity.Io;
|
|
using Microsoft.Singularity.Configuration;
|
|
|
|
using Microsoft.Contracts;
|
|
using Microsoft.SingSharp.Reflection;
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Diagnostics;
|
|
|
|
using Microsoft.Singularity.Email.Contracts;
|
|
|
|
[assembly: Transform(typeof(ApplicationResourceTransform))]
|
|
|
|
namespace Microsoft.Singularity.Email
|
|
{
|
|
[ConsoleCategory(HelpMessage="SMTP Mail Transfer Agent", DefaultAction=true)]
|
|
internal class Parameters
|
|
{
|
|
[InputEndpoint("data")]
|
|
public readonly TRef<UnicodePipeContract.Exp:READY> Stdin;
|
|
|
|
[OutputEndpoint("data")]
|
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdout;
|
|
#if false
|
|
[Endpoint]
|
|
public readonly TRef<MailStoreContract.Imp:Start> msRef;
|
|
#endif
|
|
[Endpoint]
|
|
public readonly TRef<DirectoryServiceContract.Imp:Start> nsRef;
|
|
|
|
[StringParameter("mailstore service", Mandatory=false, Default="/service/mailstore",
|
|
HelpMessage="Location of mail store service.")]
|
|
internal string mailstorePath;
|
|
|
|
[StringParameter("server", Mandatory=false, Default="0.0.0.0",
|
|
HelpMessage="Service IP Address to give to clients.")]
|
|
internal string server;
|
|
|
|
reflective internal Parameters();
|
|
|
|
internal int AppMain() {
|
|
return SmtpServer.AppMain(this);
|
|
}
|
|
}
|
|
|
|
public class Buffer
|
|
{
|
|
private byte[] data;
|
|
private int size;
|
|
|
|
public static Buffer Allocate(int bytes)
|
|
{
|
|
Buffer buffer = new Buffer(bytes);
|
|
return buffer;
|
|
}
|
|
|
|
private Buffer(int bytes)
|
|
{
|
|
this.data = new byte[bytes];
|
|
this.size = 0;
|
|
}
|
|
|
|
public byte[] Data
|
|
{
|
|
get { return data; }
|
|
}
|
|
|
|
public int Size
|
|
{
|
|
get { return size; }
|
|
}
|
|
|
|
public int Prepare(int bytes)
|
|
{
|
|
if (size + bytes > data.Length) {
|
|
byte[] dst = new byte[data.Length * 2];
|
|
Array.Copy(data, dst, size);
|
|
data = dst;
|
|
}
|
|
return size;
|
|
}
|
|
|
|
public void Save(int bytes)
|
|
{
|
|
if (size + bytes + 2 <= data.Length) {
|
|
size += bytes;
|
|
data[size++] = (byte)'\r';
|
|
data[size++] = (byte)'\n';
|
|
}
|
|
else {
|
|
throw new Exception("Overflow");
|
|
}
|
|
}
|
|
|
|
public void Release()
|
|
{
|
|
data = null;
|
|
size = 0;
|
|
}
|
|
}
|
|
|
|
public class ServerSession : SmtpSession
|
|
{
|
|
private static int idCount = 0;
|
|
private int id;
|
|
private string command;
|
|
private string server;
|
|
|
|
public ServerSession(Socket socket, String server)
|
|
: base(socket)
|
|
{
|
|
this.id = idCount++;
|
|
this.server = server;
|
|
}
|
|
|
|
public string GetCommand(String line)
|
|
{
|
|
int i = line.IndexOf(' ');
|
|
if (i < 0) {
|
|
command = line;
|
|
return null;
|
|
}
|
|
command = line.Substring(0, i).ToUpper();
|
|
return command;
|
|
}
|
|
|
|
private enum CurrentState {
|
|
HAD_NONE = 0,
|
|
HAD_HELO = 1,
|
|
HAD_MAIL = 2,
|
|
HAD_RCPT = 3,
|
|
};
|
|
|
|
public void Loop()
|
|
{
|
|
String client;
|
|
String line;
|
|
CurrentState cstate = CurrentState.HAD_NONE;
|
|
String from = null;
|
|
String to = null;
|
|
|
|
//Open a new connection to the mailstore for each smtp connection.
|
|
//Each client connection stays allive until all emails are sent.
|
|
|
|
DirectoryServiceContract.Imp! dsImp = SmtpServer.dsEp.Acquire();
|
|
|
|
MailStoreContract.Imp! msImp;
|
|
MailStoreContract.Exp! msExp;
|
|
MailStoreContract.NewChannel(out msImp, out msExp);
|
|
|
|
ErrorCode errorCode;
|
|
if (!SdsUtils.Bind(SmtpServer.mailstorePath, dsImp, msExp, out errorCode)) {
|
|
DebugStub.WriteLine("Failed to bind to mail store...error {0}\n",
|
|
__arglist(SdsUtils.ErrorCodeToString(errorCode)));
|
|
Console.WriteLine("Failed to bind to mail store...error {0}\n",
|
|
SdsUtils.ErrorCodeToString(errorCode));
|
|
|
|
DebugStub.Break();
|
|
SmtpServer.dsEp.Release(dsImp);
|
|
delete msImp;
|
|
return;
|
|
}
|
|
SmtpServer.dsEp.Release(dsImp);
|
|
|
|
// MailStoreContract.Imp! msImp = config.msRef.Acquire();
|
|
msImp.RecvMailStoreReady();
|
|
|
|
WriteLine7("220 ", server, " Singularity Simple SMTP Service Ready");
|
|
|
|
bool abort = false;
|
|
while (!abort) {
|
|
line = ReadLine7();
|
|
if (line == null) {
|
|
break;
|
|
}
|
|
|
|
GetCommand(line);
|
|
|
|
switch (command) {
|
|
case "EHLO":
|
|
client = line.Split(new Char[] {' '})[1];
|
|
Console.WriteLine(":{0}: EHLO {1}", id, client);
|
|
WriteLine7("250-", server, " greets ", client);
|
|
WriteLine7("250-8BITMIME");
|
|
WriteLine7("250 HELP");
|
|
from = null;
|
|
to = null;
|
|
cstate = CurrentState.HAD_HELO;
|
|
// DebugStub.WriteLine("Got EHLO\n");
|
|
break;
|
|
|
|
case "HELO":
|
|
client = line.Split(new Char[] {' '})[1];
|
|
Console.WriteLine(":{0}: HELO {1}", id, client);
|
|
WriteLine7("250 ", server, " greets ", client);
|
|
from = null;
|
|
to = null;
|
|
cstate = CurrentState.HAD_HELO;
|
|
// DebugStub.WriteLine("Got HELO\n");
|
|
break;
|
|
|
|
case "MAIL":
|
|
if (cstate != CurrentState.HAD_HELO) {
|
|
goto default;
|
|
}
|
|
|
|
if (line.StartsWith("MAIL FROM:")) {
|
|
int pos = line.IndexOf("<") + 1;
|
|
int beg = line.LastIndexOf(':');
|
|
if (beg < pos) {
|
|
beg = pos;
|
|
}
|
|
int end = line.IndexOf('>');
|
|
from = line.Substring(beg, end - beg).ToLower();
|
|
WriteLine7("250 OK");
|
|
cstate = CurrentState.HAD_MAIL;
|
|
// DebugStub.WriteLine("Got MAIL\n");
|
|
}
|
|
else {
|
|
WriteLine7("501 Syntax error in parameters");
|
|
}
|
|
break;
|
|
|
|
case "RCPT":
|
|
if (cstate != CurrentState.HAD_MAIL && cstate != CurrentState.HAD_RCPT) {
|
|
goto default;
|
|
}
|
|
// DebugStub.WriteLine("Got RCPT\n");
|
|
if (line.StartsWith("RCPT TO:")) {
|
|
int pos = line.IndexOf("<") + 1;
|
|
int beg = line.LastIndexOf(':');
|
|
if (beg < pos) {
|
|
beg = pos;
|
|
}
|
|
int end = line.IndexOf('>');
|
|
string address = line.Substring(beg, end - beg).ToLower();
|
|
|
|
if (SmtpServer.addresses.ContainsKey(address)) {
|
|
WriteLine7("250 OK");
|
|
if (to != null) {
|
|
to = to + ";" + address;
|
|
}
|
|
else {
|
|
to = address;
|
|
}
|
|
cstate = CurrentState.HAD_RCPT;
|
|
}
|
|
else {
|
|
WriteLine7("250 Accepting for forwarding.");
|
|
cstate = CurrentState.HAD_RCPT;
|
|
if (to != null) {
|
|
to = to + ";" + "mail_forward@enron.com";
|
|
}
|
|
else {
|
|
to = "mail_forward@enron.com";
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
WriteLine7("501 Syntax error in parameters");
|
|
}
|
|
break;
|
|
|
|
case "DATA":
|
|
if (cstate != CurrentState.HAD_RCPT) {
|
|
goto default;
|
|
}
|
|
// DebugStub.WriteLine("Got DATA\n");
|
|
WriteLine7("354 Start mail input; end with <CRLF>.<CRLF>");
|
|
|
|
bool old = Verbose;
|
|
Verbose = false;
|
|
Buffer buffer = Buffer.Allocate(65536);
|
|
bool good = false;
|
|
|
|
for (;;) {
|
|
int off = buffer.Prepare(1024);
|
|
int len = ReadLine8(buffer.Data, off);
|
|
|
|
if (len < 0) {
|
|
break;
|
|
}
|
|
|
|
if (len == 1 && buffer.Data[off] == '.') {
|
|
good = true;
|
|
break;
|
|
}
|
|
else {
|
|
#if false
|
|
Console.WriteLine("S: {0}",
|
|
Encoding.ASCII.GetString(buffer.Data, off, len));
|
|
#endif
|
|
buffer.Save(len);
|
|
}
|
|
}
|
|
Verbose = old;
|
|
|
|
if (good) {
|
|
bool succeeded = false;
|
|
char[] in ExHeap! addresses = Bitter.FromString(to);
|
|
byte[] in ExHeap! data
|
|
= Bitter.FromByteArray(buffer.Data, 0, buffer.Size);
|
|
|
|
// MailStoreContract.Imp msImp = SmtpServer.msEp.Acquire();
|
|
// DebugStub.WriteLine("Saving data\n");
|
|
Console.WriteLine("SmtpAgaint: Email from {0} to {1}", from, to);
|
|
msImp.SendSaveMessage(addresses, data);
|
|
switch receive
|
|
{
|
|
case msImp.SaveAck():
|
|
succeeded = true;
|
|
break;
|
|
|
|
case msImp.SaveNak(error):
|
|
Console.WriteLine("SmtpAgent: Server dropped email, "+
|
|
"error={0}", error);
|
|
break;
|
|
|
|
case msImp.ChannelClosed():
|
|
break;
|
|
}
|
|
// SmtpServer.msEp.Release(msImp);
|
|
if (succeeded) {
|
|
WriteLine7("250 OK");
|
|
}
|
|
else {
|
|
WriteLine7("554 Transaction failed");
|
|
}
|
|
}
|
|
else {
|
|
WriteLine7("554 Transaction failed");
|
|
Dump();
|
|
throw new Exception("554 Transaction failed");
|
|
}
|
|
|
|
buffer.Release();
|
|
cstate = CurrentState.HAD_HELO;
|
|
from = null;
|
|
to = null;
|
|
break;
|
|
|
|
case "NOOP":
|
|
WriteLine7("250 OK");
|
|
break;
|
|
|
|
case "HELP":
|
|
WriteLine7("250 OK");
|
|
break;
|
|
|
|
case "RSET":
|
|
WriteLine7("250 OK");
|
|
from = null;
|
|
to = null;
|
|
cstate = CurrentState.HAD_HELO;
|
|
break;
|
|
|
|
case "QUIT":
|
|
WriteLine7("221 ", server, " Service closing transmission channel");
|
|
abort = true;
|
|
break;
|
|
|
|
default:
|
|
WriteLine7("503 Unrecognized command [", command, "]");
|
|
abort = true;
|
|
break;
|
|
}
|
|
}
|
|
Console.WriteLine(":{0}: Session closed", id);
|
|
delete msImp;
|
|
Close();
|
|
}
|
|
}
|
|
|
|
public class SmtpServer
|
|
{
|
|
public const int _port = 25;
|
|
public static bool verbose;
|
|
|
|
// public static TRef<MailStoreContract.Imp:ReadyState> msEp;
|
|
public static TRef<DirectoryServiceContract.Imp:Ready> dsEp;
|
|
|
|
public static SortedList addresses;
|
|
public static string mailstorePath;
|
|
|
|
internal static int AppMain(Parameters! config)
|
|
{
|
|
string server = config.server;
|
|
if (server[0] >= '0' && server[0] <= '9') {
|
|
server = "[" + server + "]";
|
|
}
|
|
|
|
// Connect to the MailStore.
|
|
Console.WriteLine("SmtpAgent: Connecting to MailStore.");
|
|
DirectoryServiceContract.Imp! dsImp = config.nsRef.Acquire();
|
|
dsImp.RecvSuccess();
|
|
|
|
MailStoreContract.Imp! msImp;
|
|
MailStoreContract.Exp! msExp;
|
|
MailStoreContract.NewChannel(out msImp, out msExp);
|
|
|
|
mailstorePath = config.mailstorePath;
|
|
|
|
ErrorCode error;
|
|
if (!SdsUtils.Bind(config.mailstorePath, dsImp, msExp, out error)) {
|
|
DebugStub.WriteLine("Failed to bind to mail store...error {0}\n",
|
|
__arglist(SdsUtils.ErrorCodeToString(error)));
|
|
Console.WriteLine("Failed to bind to mail store...error {0}\n",
|
|
SdsUtils.ErrorCodeToString(error));
|
|
|
|
delete dsImp;
|
|
delete msImp;
|
|
return -1;
|
|
}
|
|
|
|
|
|
// MailStoreContract.Imp! msImp = config.msRef.Acquire();
|
|
msImp.RecvMailStoreReady();
|
|
|
|
// Retrieve vaild address list.
|
|
char[] in ExHeap! buffer;
|
|
msImp.SendGetAddressList();
|
|
msImp.RecvGetAck(out buffer);
|
|
addresses = ReadUniqueList(Bitter.ToString(buffer));
|
|
delete buffer;
|
|
|
|
delete msImp;
|
|
// Save the endpoint.
|
|
// msEp = new TRef<MailStoreContract.Imp:ReadyState>(msImp);
|
|
dsEp = new TRef<DirectoryServiceContract.Imp:Ready>(dsImp);
|
|
|
|
// Connect to the network.
|
|
Console.WriteLine("SmtpAgent: Opening TCP port {0}", _port);
|
|
Socket target = new Socket(AddressFamily.InterNetwork,
|
|
SocketType.Stream, ProtocolType.Tcp);
|
|
target.Bind(new IPEndPoint(IPAddress.Any, _port));
|
|
target.Listen((int)SocketOptionName.MaxConnections);
|
|
|
|
for (bool stop = false; !stop;) {
|
|
Console.WriteLine(":: {0} waiting for accept.", server);
|
|
Socket socket = target.Accept();
|
|
|
|
// Create the Session and kick it off in its own thread.
|
|
ServerSession conn = new ServerSession(socket, server);
|
|
|
|
conn.Verbose = verbose;
|
|
Thread thread = new Thread(conn.Loop);
|
|
thread.Start();
|
|
}
|
|
target.Close();
|
|
|
|
// Close the Store engine.
|
|
dsImp = dsEp.Acquire();
|
|
if (dsImp != null) {
|
|
delete dsImp;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
public static string PopNextAddress(ref string content)
|
|
{
|
|
if (content.Length == 0) {
|
|
return null;
|
|
}
|
|
|
|
int i = content.IndexOf(';');
|
|
string ret = (i > 0) ? content.Substring(0, i) : content;
|
|
content = (i > 0) ? content.Substring(i + 1) : "";
|
|
return ret;
|
|
}
|
|
|
|
public static SortedList ReadUniqueList(string content)
|
|
{
|
|
SortedList list = new SortedList();
|
|
string address;
|
|
|
|
while ((address = PopNextAddress(ref content)) != null) {
|
|
if (!list.ContainsKey(address)) {
|
|
list.Add(address, address);
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
}
|
|
}
|
|
|
|
|