5using System.Threading.Tasks;
34 outgoingMails.Removed += OutgoingMails_Removed;
36 Log.Terminating += (Sender, e) =>
40 return Task.CompletedTask;
67 public override string Type =>
"SMTP";
72 if (
string.Compare(
Type,
"chat",
true) == 0)
74 string Key = From.Address +
" | " + To.
Address;
75 outgoingMails.
TryGetValue(Key, out OutgoingMail Mail);
83 string Markdown =
null;
84 string Subject =
null;
85 string ReplaceId =
null;
86 bool Immediate =
false;
90 if (N is XmlElement E)
95 Subject = E.InnerText;
103 if (E.NamespaceURI ==
"urn:xmpp:message-correct:0")
108 foreach (XmlNode N2
in E.ChildNodes)
110 if (N2 is XmlElement E2 && E2.LocalName ==
"body")
119 switch (E.NamespaceURI)
133 Markdown = E.InnerText;
141 byte[] Bin = Convert.FromBase64String(E.InnerText);
152 TransferDecoded = Bin
166 if (!(Markdown is
null) && (Text is
null || Html is
null))
175 if (Subject is
null && Text is
null && Html is
null)
180 Mail =
new OutgoingMail()
187 outgoingMails[Key] = Mail;
190 if (!
string.IsNullOrEmpty(ReplaceId) &&
191 !(Mail.ById is
null) &&
192 Mail.ById.TryGetValue(ReplaceId, out OutgoingMailRec Rec))
200 if (
string.IsNullOrEmpty(Mail.Subject))
201 Mail.Subject = Subject;
202 else if (Mail.Subject != Subject && !
string.IsNullOrEmpty(Subject))
205 Mail.Subject = Subject;
206 Mail.Records.Clear();
210 if (
string.IsNullOrEmpty(
Id) && !
string.IsNullOrEmpty(ReplaceId))
213 Rec =
new OutgoingMailRec()
221 Mail.Records.AddLast(Rec);
223 if (!
string.IsNullOrEmpty(
Id))
225 Mail.ById ??=
new Dictionary<string, OutgoingMailRec>();
231 outgoingMails.
Remove(Key);
254 private static async Task<bool> Send(OutgoingMail Mail)
256 List<EmbeddedContent> Attachments =
null;
257 List<EmbeddedContent> Inline =
null;
258 StringBuilder PlainText =
null;
259 StringBuilder Html =
null;
262 foreach (OutgoingMailRec Rec
in Mail.Records)
264 if (
Id is
null && !
string.IsNullOrEmpty(Rec.Id))
267 if (!
string.IsNullOrEmpty(Rec.Text))
269 PlainText ??=
new StringBuilder();
270 PlainText.AppendLine(Rec.Text.Trim());
273 if (!
string.IsNullOrEmpty(Rec.Html))
275 Html ??=
new StringBuilder();
276 Html.AppendLine(Rec.Html.Trim());
279 if (!(Rec.Embedded is
null))
286 Inline ??=
new List<EmbeddedContent>();
287 Inline.Add(Embedded);
292 Attachments ??=
new List<EmbeddedContent>();
293 Attachments.Add(Embedded);
300 List<EmbeddedContent> Alternatives =
new List<EmbeddedContent>();
301 string Subject = Mail.Subject;
304 if (!(PlainText is
null))
306 s = PlainText.ToString();
311 Raw = Encoding.UTF8.GetBytes(s)
314 if (
string.IsNullOrEmpty(Subject))
315 Subject = FirstRow(s);
325 Raw = Encoding.UTF8.GetBytes(s)
328 if (!(Inline is
null) && Inline.Count > 0)
353 Alternatives.Add(HtmlContent);
355 if (
string.IsNullOrEmpty(Subject))
358 LinkedList<HtmlNode> ToProcess =
new LinkedList<HtmlNode>();
359 string FirstHeader =
null;
360 int FirstHeaderLevel =
int.MaxValue;
361 string FirstParagraph =
null;
364 ToProcess.AddLast(N);
366 while (!(ToProcess.First is
null))
369 ToProcess.RemoveFirst();
373 if (
string.Compare(E.FullName,
"P",
true) == 0)
374 FirstParagraph ??= GetText(E);
375 else if (E.FullName.ToUpper().StartsWith(
'H') &&
int.TryParse(E.FullName[1..], out
int Level))
377 if (Level < FirstHeaderLevel)
379 FirstHeader = GetText(E);
380 FirstHeaderLevel = Level;
387 ToProcess.AddLast(N2);
392 Subject = FirstRow(FirstHeader);
393 if (
string.IsNullOrEmpty(Subject))
394 Subject = FirstRow(FirstParagraph);
398 if (
string.IsNullOrEmpty(Subject))
401 return await Mail.Endpoint.smtpServer.SendMessage(Mail.From, Mail.To, Subject,
Id, Alternatives.ToArray(), Attachments?.ToArray());
404 private static string FirstRow(
string s)
411 return s[..i].TrimEnd();
416 StringBuilder Output =
new StringBuilder();
418 return Output.ToString();
421 private static void GetText(
HtmlElement E, StringBuilder Output)
423 if (
string.Compare(E.
FullName,
"BR",
true) == 0)
433 Output.Append(Text.InlineText);
446 await this.xmppServer.Presence(
"subscribed",
Id, From, To, Language,
string.Empty,
this);
448 StringBuilder Markdown =
new StringBuilder();
450 Markdown.AppendLine(
"e-Mail Invitation");
451 Markdown.AppendLine(
"======================");
452 Markdown.AppendLine();
454 Markdown.Append(
"**");
456 Markdown.Append(
"** has added you to its white-list. This means you can send e-mail to **");
458 Markdown.Append(
"** from **");
460 Markdown.AppendLine(
"**.");
462 if (!await this.xmppServer.SendMailMessage(From.
BareJid, To.
BareJid,
"Added to white-list", Markdown.
ToString()))
469 await this.xmppServer.Presence(
"unsubscribed",
Id, From, To, Language,
string.Empty,
this);
471 Markdown =
new StringBuilder();
473 Markdown.AppendLine(
"White-list removal");
474 Markdown.AppendLine(
"======================");
475 Markdown.AppendLine();
477 Markdown.Append(
"**");
479 Markdown.Append(
"** has removed you from its white-list. This means you can no longer send e-mail to **");
481 Markdown.Append(
"** from **");
483 Markdown.AppendLine(
"**.");
485 if (!await this.xmppServer.SendMailMessage(From.
BareJid, To.
BareJid,
"Removed from white-list", Markdown.
ToString()))
492 Markdown =
new StringBuilder();
494 Markdown.AppendLine(
"Request approved");
495 Markdown.AppendLine(
"===================");
496 Markdown.AppendLine();
498 Markdown.Append(
"Your request to be permitted to send e-mail to **");
500 Markdown.Append(
"** from **");
502 Markdown.AppendLine(
"** has been approved.");
504 if (!await this.xmppServer.SendMailMessage(From.
BareJid, To.
BareJid,
"White-listing request approved", Markdown.
ToString()))
511 Markdown =
new StringBuilder();
513 Markdown.AppendLine(
"Request rejected");
514 Markdown.AppendLine(
"===================");
515 Markdown.AppendLine();
517 Markdown.Append(
"Your request to be permitted to send e-mail to **");
519 Markdown.Append(
"** from **");
521 Markdown.AppendLine(
"** has been rejected.");
523 if (!await this.xmppServer.SendMailMessage(From.
BareJid, To.
BareJid,
"White-listing request rejected", Markdown.
ToString()))
538 StringBuilder sb =
new StringBuilder();
540 sb.Append(
"IQ stanzas not supported for mail recipients. From: ");
545 return !(await Sender.IqErrorServiceUnavailable(
Id, From, To, sb.
ToString(),
"en") is
null);
554 return this.
IQ(Type,
Id, To, From, Language,
Stanza?.Content, Sender);
560 return this.
Message(Type,
Id, To, From, Language,
Stanza?.Content, Sender);
566 return this.
Presence(Type,
Id, To, From, Language,
Stanza?.Content, Sender);
572 return Task.FromResult(
true);
578 return Task.FromResult(
string.Empty);
584 return Task.FromResult(
true);
590 return this.
Message(Type,
Id, To, From, Language, ContentXml,
null);
596 return Task.FromResult(
true);
602 return this.
Presence(Type,
Id, To, From, Language, ContentXml,
null);
608 return Task.FromResult(
true);
614 return Task.FromResult(
true);
625 StringBuilder Received =
new StringBuilder();
627 Received.Append(
"from ");
628 Received.Append(
Message.ClientName);
629 Received.Append(
" [");
630 Received.Append(
Message.RemoteEndPoint);
631 Received.Append(
"] by ");
632 Received.Append(this.smtpServer.Domain);
633 Received.Append(
" with ");
634 Received.Append(typeof(
SmtpServer).Namespace);
635 Received.Append(
" ");
636 Received.Append(typeof(
SmtpServer).Assembly.ImageRuntimeVersion);
637 Received.Append(
"; ");
640 KeyValuePair<string, string>[] Headers =
new KeyValuePair<string, string>[Message.AllHeaders.Length + 1];
642 Message.AllHeaders.CopyTo(Headers, 1);
643 Headers[0] =
new KeyValuePair<string, string>(
"Received", Received.ToString());
645 return this.smtpServer.SendMessage(
Message.FromMail.Address, Recipient.Address, Headers,
Message.UntransformedBody, DateTime.Now);
Helps with parsing of commong data types.
static readonly char[] CRLF
Contains the CR LF character sequence.
static string EncodeRfc822(DateTime Timestamp)
Encodes a date and time, according to RFC 822 §5.
Contains information about a response to a content request.
byte[] Encoded
Encoded object.
string ContentType
Internet Content-Type of encoded object.
bool HasError
If an error occurred.
Exception Error
Error response.
string Content
CDATA Content
const string DefaultContentType
Default Content-Type for HTML: text/html
static string GetBody(string Html)
Extracts the contents of the BODY element in a HTML string.
Body Body
First BODY element of document, if found, null otherwise.
Base class for all HTML elements.
bool HasChildren
If the element has children.
string FullName
Element full name (including prefix).
IEnumerable< HtmlNode > Children
Child nodes, or null if none.
Base class for all HTML nodes.
Static class managing encoding and decoding of internet content.
static Task< ContentResponse > EncodeAsync(object Object, Encoding Encoding, params string[] AcceptedContentTypes)
Encodes an object.
const string ContentType
Markdown content type.
Contains a markdown document. This markdown document class supports original markdown,...
static string Encode(string s)
Encodes all special characters in a string so that it can be included in a markdown document without ...
async Task< string > GeneratePlainText()
Generates Plain Text from the markdown text.
async Task< string > GenerateHTML()
Generates HTML from the markdown text.
static Task< MarkdownDocument > CreateAsync(string MarkdownText, params Type[] TransparentExceptionTypes)
Contains a markdown document. This markdown document class supports original markdown,...
Represents content embedded in other content.
ContentDisposition Disposition
Disposition of embedded object.
string ContentType
Content-Type of embedded object.
Represents related content, encoded with multipart/related
Plain text encoder/decoder.
const string DefaultContentType
text/plain
Helps with common XML-related tasks.
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Static class managing the application event log. Applications and services log events on this static ...
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
static void Informational(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an informational event.
void Exception(Exception Exception)
Called to inform the viewer of an exception state.
ISniffer[] Sniffers
Registered sniffers.
Represents one message received over SMTP
Implements a simple SMTP Server, as defined in:
Abstract base class for server connections.
CaseInsensitiveString LocalDomain
Local domain name.
CaseInsensitiveString RemoteDomain
Connection to domain.
Manages the connection with an SMTP server.
override async Task< bool > IQ(string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml, ISender Sender)
Sends an IQ stanza. If stanza was sent.
SmtpS2SEndpoint(CaseInsensitiveString LocalDomain, CaseInsensitiveString RemoteDomain, SmtpServer SmtpServer, XmppServer XmppServer, params ISniffer[] Sniffers)
Manages the connection with an SMTP server.
override Task< bool > IqError(string Id, XmppAddress To, XmppAddress From, string ErrorXml)
Sends an IQ error stanza. If stanza was sent.
override Task< bool > IQ(string Type, string Id, XmppAddress To, XmppAddress From, string Language, Stanza Stanza, ISender Sender)
Sends an IQ stanza. If stanza was sent.
override Task< bool > Message(string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml)
Sends a message stanza. If stanza was sent.
override Task< bool > Presence(string Type, string Id, XmppAddress To, XmppAddress From, string Language, Stanza Stanza, ISender Sender)
Sends a presence stanza. If stanza was sent.
override Task< bool > IqResult(string Id, XmppAddress To, XmppAddress From, string ResultXml)
Sends an IQ result stanza. If stanza was sent.
override Task< string > IqError(string Id, XmppAddress To, XmppAddress From, Exception ex)
Sends an IQ error stanza. If stanza was sent.
Task< bool > RelayMessage(SmtpMessage Message, MailAddress Recipient)
Relays a mail message
override Task< bool > PresenceError(string Id, XmppAddress To, XmppAddress From, Exception ex)
Sends a presence error stanza. If stanza was sent.
override Task< bool > Presence(string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml)
Sends a presence stanza. If stanza was sent.
override Task< bool > PresenceError(string Id, XmppAddress To, XmppAddress From, string ErrorXml)
Sends a presence error stanza. If stanza was sent.
bool TrustServer
If server should be trusted, regardless if the operating system could validate its certificate or not...
override async Task< bool > Message(string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml, ISender Sender)
Sends a message stanza. If stanza was sent.
override string Type
Type of endpoint
override async Task< bool > Presence(string Type, string Id, XmppAddress To, XmppAddress From, string Language, string ContentXml, ISender Sender)
Sends a presence stanza. If stanza was sent.
override Task< bool > MessageError(string Id, XmppAddress To, XmppAddress From, Exception ex)
Sends a message error stanza. If stanza was sent.
override Task< bool > Message(string Type, string Id, XmppAddress To, XmppAddress From, string Language, Stanza Stanza, ISender Sender)
Sends a message stanza. If stanza was sent.
Contains information about a stanza.
XmlElement StanzaElement
Stanza element.
Contains information about one XMPP address.
override string ToString()
object.ToString()
CaseInsensitiveString Address
XMPP Address
CaseInsensitiveString BareJid
Bare JID
const string ContentNamespace
urn:xmpp:content
const string MailNamespace
urn:xmpp:smtp
Represents a case-insensitive string.
string Value
String-representation of the case-insensitive string. (Representation is case sensitive....
override string ToString()
Implements an in-memory cache.
void Dispose()
IDisposable.Dispose
bool Remove(KeyType Key)
Removes an item from the cache.
bool TryGetValue(KeyType Key, out ValueType Value)
Tries to get a value from the cache.
Event arguments for cache item removal events.
ValueType Value
Value of item that was removed.
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
Interface for senders of stanzas.
ContentDisposition
Content disposition
ContentType
DTLS Record content type.