singrdk/base/Applications/EmailServer/MailStore/MailStore.sg

388 lines
14 KiB
Plaintext

///////////////////////////////////////////////////////////////////////////////
//
// Microsoft Research Singularity
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Note: Mail storage server.
//
using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
using Microsoft.SingSharp;
using Microsoft.Contracts;
using Microsoft.SingSharp.Reflection;
using Microsoft.Singularity;
using Microsoft.Singularity.Applications;
using Microsoft.Singularity.Channels;
using Microsoft.Singularity.Configuration;
using Microsoft.Singularity.Directory;
using Microsoft.Singularity.Email.Contracts;
using Microsoft.Singularity.Io;
using Microsoft.Singularity.Security;
[assembly: Transform(typeof(ApplicationResourceTransform))]
[assembly: ApplicationPublisherAttribute("singularity.microsoft.com")]
[assembly: AssertPrivilegeAttribute("$register-privilege.localhost")]
namespace Microsoft.Singularity.Email
{
[ConsoleCategory(HelpMessage="Mail Store Service", DefaultAction=true)]
internal class Parameters
{
[InputEndpoint("data")]
public readonly TRef<UnicodePipeContract.Exp:READY> Stdin;
[OutputEndpoint("data")]
public readonly TRef<UnicodePipeContract.Imp:READY> Stdout;
[Endpoint]
public readonly TRef<DirectoryServiceContract.Imp:Start> nsRef;
[Endpoint]
public readonly TRef<AntiVirusContract.Imp:Start> avRef;
[StringParameter("accounts", Mandatory=false, Default="/init/accounts",
HelpMessage="File with email accounts.")]
internal string accountPath;
[StringParameter("files", Mandatory=false, Default="/fs/email",
HelpMessage="Directory for email files.")]
internal string fileRoot;
reflective internal Parameters();
internal int AppMain() {
return Store.AppMain(this);
}
}
public class Account
{
public string address;
public string path;
public int file;
public static SortedList accounts;
public Account(string address, string path)
{
this.address = address;
this.path = path;
this.file = 0;
}
public string NextFile()
{
return this.path + "/" + (this.file++).ToString();
}
public static void ReadAllFromFile(string path)
{
SortedList list = new SortedList(256);
// Open file RO for now
FileStream fsInput = new FileStream(path, FileMode.Open, FileAccess.Read);
StreamReader srInput = new StreamReader(fsInput);
String line;
while ((line = srInput.ReadLine()) != null) {
int colon = line.IndexOf(':');
if (colon > 0) {
string address = line.Substring(colon + 1);
string storage = line.Substring(0, colon);
if (!list.ContainsKey(address)) {
list.Add(address, new Account(address, storage));
}
}
}
//closing a stream reader will close the file stream under the covers
//if you close the file stream afterwards, you will deadlock.
srInput.Close();
accounts = list;
}
public static string AddressList()
{
string list = null;
foreach (Account account in accounts.Values) {
if (account != null) {
if (list != null) {
list = list + ";" + account.address;
}
else {
list = account.address;
}
}
}
return list;
}
}
public class Store
{
public static TRef<AntiVirusContract.Imp:ReadyState> avEp;
// This function initializes the store, registers with the name service and
// starts a new thread per SMTP agent.
//
internal static int AppMain(Parameters! config)
{
DirectoryServiceContract.Imp ds = (config.nsRef).Acquire();
if (ds == null) {
throw new Exception("Unable to acquire handle to the Directory Service root");
}
ds.RecvSuccess();
// Here is the channel we use to communicate with the NameServer
ServiceProviderContract.Imp! nsImp;
ServiceProviderContract.Exp! nsExp;
ServiceProviderContract.NewChannel(out nsImp, out nsExp);
try {
ds.SendRegister(Bitter.FromString2(MailStoreContract.ModuleName), nsImp);
switch receive {
case ds.AckRegister() :
// All is well.
break;
case ds.NakRegister(ServiceProviderContract.Imp:Start rejectedEP, error) :
// All is very much not well; abort.
Console.WriteLine("MailStore: Failed to register as {0}: {1}",
MailStoreContract.ModuleName, error);
delete nsExp;
delete rejectedEP;
return -1;
case ds.ChannelClosed():
Console.WriteLine("mailStore: ds channel closed");
delete nsExp;
return -1;
}
Console.WriteLine("MailStore: Reading account list.");
Account.ReadAllFromFile(config.accountPath);
// Make sure we have a directory for each account.
{
ErrorCode error;
// Directory.CreateDirectory(config.fileRoot);
if (SdsUtils.CreateDirectory(config.fileRoot, ds, out error)) {
Console.WriteLine("MailStore: Created {0}", config.fileRoot);
}
}
foreach (Account account in Account.accounts.Values) {
if (account == null || account.path == null) {
continue;
}
account.path = config.fileRoot + "/" + account.path;
try {
ErrorCode error;
string path = account.path;
if (SdsUtils.CreateDirectory(path, ds, out error)) {
Console.WriteLine("MailStore: Created {0}", path);
}
int count = 0;
for (;;) {
string file = path + "/" + count.ToString();
FileAttributesRecord atr;
if (!SdsUtils.GetAttributes(file, ds, out atr, out error)) {
if (count > 0) {
Console.WriteLine("MailStore: {0}", file);
}
break;
}
count++;
}
account.file = count;
}
catch (IOException) {
}
}
}
finally {
delete ds;
}
Console.WriteLine("MailStore: Connecting to AntiVirus.");
AntiVirusContract.Imp! avImp = config.avRef.Acquire();
avImp.RecvAntiVirusReady();
avEp = new TRef<AntiVirusContract.Imp:ReadyState>(avImp);
Console.WriteLine("MailStore: Ready at {0}", MailStoreContract.ModuleName);
// Here is the set of client channels we service
for (;;) {
switch receive {
// ------------------------------- Requests for new connections
case nsExp.Connect(ServiceContract.Exp:Start! newEp):
// We expect people top give us MailStoreContract.Exp instances
MailStoreContract.Exp newClient = newEp as MailStoreContract.Exp;
if (newClient == null) {
// Invalid contract type. Fail.
nsExp.SendNackConnect(newEp);
}
else {
// Signal ready and start servicing this contract
nsExp.SendAckConnect();
// Launch the thread to handle the incoming requests.
Store child = new Store(newClient);
Thread thread = new Thread(child.Run);
thread.Start();
}
break;
case nsExp.ChannelClosed():
// The namespace channel is closed so quit.
delete nsExp;
return -1;
}
}
// Close the Anti-Virus Scanner.
avImp = avEp.Acquire();
if (avImp != null) {
delete avImp;
}
return 0;
}
public static string PopNextAddress(ref string content)
{
if (content == null || 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 Store([Claims] MailStoreContract.Exp:Start! ep)
{
epRef = new TRef<MailStoreContract.Exp:Start>(ep);
}
private TRef<MailStoreContract.Exp:Start> epRef;
public void Run()
{
MailStoreContract.Exp ep = epRef.Acquire();
if (ep == null) {
return;
}
ep.SendMailStoreReady();
int arg;
for (;;) {
switch receive {
case ep.SaveMessage(char[]! in ExHeap boxes, byte[]! in ExHeap buffer):
// Do something with the message.
int error = 0;
#if false
Console.WriteLine("MailStore: Processing Message");
#endif
byte[] message = Bitter.ToByteArray(buffer);
string accountList = Bitter.ToString(boxes);
delete boxes;
AntiVirusContract.Imp avImp = avEp.Acquire();
avImp.SendCheckFile(buffer);
string virus = null;
switch receive
{
case avImp.FileClean():
break;
case avImp.FileContaminated(virusVector):
virus = Bitter.ToString(virusVector);
delete virusVector;
break;
case avImp.ChannelClosed():
virus = "**Scanner Failed**";
break;
}
avEp.Release(avImp);
if (virus != null) {
// save the message to each of the boxes.
Console.WriteLine("MailStore: "+
"Discarding message contaminated with {0}.",
virus);
error = 666;
}
else {
error = 0;
string address;
while ((address = PopNextAddress(ref accountList)) != null) {
#if false
Console.WriteLine("MailStore: Receive for {0} of {1} bytes",
address, message.Length);
#endif
int key = Account.accounts.IndexOfKey(address);
if (key >= 0) {
Account! account = (Account!)Account.accounts.GetByIndex(key);
string path = account.NextFile();
try {
FileStream! sw = (!)File.Create(path);
sw.Write(message, 0, message.Length);
sw.Flush();
sw.Close();
}
catch (IOException) {
Console.WriteLine("MailStore: Write to {0} failed.", path);
error = 665;
}
}
else {
Console.WriteLine("MailStore: Bad address {0}", address);
}
}
}
if (error == 0) {
ep.SendSaveAck();
}
else {
ep.SendSaveNak(error);
}
break;
case ep.GetAddressList():
Console.WriteLine("MailStore: Providing list of email addresses.");
string list = Account.AddressList();
char[]! in ExHeap buffer = (!)Bitter.FromString(list);
ep.SendGetAck(buffer);
break;
case ep.ChannelClosed():
delete ep;
return;
}
}
}
}
}