MailSharp / MailSharp.POP3 / Server / Pop3Server.cs
Code · 139 lines · 4765 bytes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139using MailSharp.Common;
using MailSharp.Common.Services;
using MailSharp.POP3.Metrics;
using MailSharp.POP3.Session;
using System.Net;
using System.Net.Sockets;

namespace MailSharp.POP3.Server;

public class Pop3Server
{
	private readonly IConfiguration configuration;
	private readonly ILogger<Pop3Server> logger;
	private readonly ILogger<Pop3Session> sessionLogger;
	private readonly AuthenticationService authService;
	private readonly MailboxService mailboxService;
	private readonly List<ServerContext> listeners = [];
	private readonly Pop3Metrics pop3Metrics;
	private CancellationTokenSource? cts;

	public Pop3Server(
		IConfiguration configuration,
		ILogger<Pop3Server> logger,
		ILogger<Pop3Session> sessionLogger,
		AuthenticationService authService,
		MailboxService mailboxService,
		Pop3Metrics pop3Metrics)
	{
		this.configuration = configuration;
		this.logger = logger;
		this.sessionLogger = sessionLogger;
		this.authService = authService;
		this.mailboxService = mailboxService;
		this.pop3Metrics = pop3Metrics;

		var ports = configuration.GetSection("Pop3Settings:Ports").Get<List<PortConfig>>()
			?? throw new InvalidOperationException("Ports not configured");

		foreach (var port in ports)
		{
			listeners.Add(new(new TcpListener(IPAddress.Parse(port.Host), port.Port), port.Security));
		}
	}

	public async Task StartAsync()
	{
		cts = new CancellationTokenSource();
		var active = new List<ServerContext>();
		foreach (var context in listeners)
		{
			try
			{
				context.Listener.Start();
				var eventIdConfig = configuration.GetSection("Pop3EventIds:ServerStarted").Get<EventIdConfig>()
					?? throw new InvalidOperationException("Missing Pop3EventIds:ServerStarted");
				logger.LogInformation(
					new EventId(eventIdConfig.Id, eventIdConfig.Name),
					configuration["Pop3LogMessages:ServerStarted"],
					context.Listener.LocalEndpoint, context.Security);
				active.Add(context);
			}
			catch (SocketException ex)
			{
				var eventIdConfig = configuration.GetSection("Pop3EventIds:ServerStartFailed").Get<EventIdConfig>()
					?? throw new InvalidOperationException("Missing Pop3EventIds:ServerStartFailed");
				logger.LogError(
					new EventId(eventIdConfig.Id, eventIdConfig.Name),
					ex,
					configuration["Pop3LogMessages:ServerStartFailed"],
					((IPEndPoint)context.Listener.LocalEndpoint).Port,
					ex.Message);
			}
		}
		if (active.Count > 0)
			await Task.WhenAll(active.Select(context =>
				Task.Run(() => AcceptClientsAsync(context, cts.Token), cts.Token)));
		await StopAsync();
	}

	private async Task AcceptClientsAsync(ServerContext context, CancellationToken cancellationToken)
	{
		while (!cancellationToken.IsCancellationRequested)
		{
			try
			{
				var client = await context.Listener.AcceptTcpClientAsync(cancellationToken);
				var eventIdConfig = configuration.GetSection("Pop3EventIds:ClientAccepted").Get<EventIdConfig>()
					?? throw new InvalidOperationException("Missing Pop3EventIds:ClientAccepted");
				logger.LogInformation(
					new EventId(eventIdConfig.Id, eventIdConfig.Name),
					configuration["Pop3LogMessages:ClientAccepted"],
					client.Client.RemoteEndPoint);

				var session = new Pop3Session(client, configuration, context.Security, authService, mailboxService, pop3Metrics, sessionLogger);
				_ = session.ProcessAsync(cancellationToken);
			}
			catch (OperationCanceledException)
			{
				var eventIdConfig = configuration.GetSection("Pop3EventIds:ListenerStopped").Get<EventIdConfig>()
					?? throw new InvalidOperationException("Missing Pop3EventIds:ListenerStopped");
				logger.LogInformation(
					new EventId(eventIdConfig.Id, eventIdConfig.Name),
					configuration["Pop3LogMessages:ListenerStopped"],
					context.Listener.LocalEndpoint);
				break;
			}
			catch (Exception ex)
			{
				var eventIdConfig = configuration.GetSection("Pop3EventIds:ClientAcceptError").Get<EventIdConfig>()
					?? throw new InvalidOperationException("Missing Pop3EventIds:ClientAcceptError");
				logger.LogError(
					new EventId(eventIdConfig.Id, eventIdConfig.Name),
					ex,
					configuration["Pop3LogMessages:ClientAcceptError"],
					context.Listener.LocalEndpoint);
			}
		}
	}

	public async Task StopAsync()
	{
		if (cts != null)
		{
			await cts.CancelAsync();
		}
		foreach (var context in listeners)
		{
			context.Listener.Stop();
			var eventIdConfig = configuration.GetSection("Pop3EventIds:ListenerStopped").Get<EventIdConfig>()
				?? throw new InvalidOperationException("Missing Pop3EventIds:ListenerStopped");
			logger.LogInformation(
				new EventId(eventIdConfig.Id, eventIdConfig.Name),
				configuration["Pop3LogMessages:ListenerStopped"],
				context.Listener.LocalEndpoint);
			context.Listener.Dispose();
		}
		listeners.Clear();
	}
}