Под .NET такой код отлично работает:
using System;
using System.Collections;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using Mono.Security.Protocol.Tls;
using System.Security.Cryptography.X509Certificates;
namespace Pop3Test
{
class Pop3Auth
{
private const int readtimeout = 10000;
private const int writetimeout = 10000;
private static Hashtable certificateErrors = new Hashtable ();
// The following method is invoked by the RemoteCertificateValidationDelegate.
private static bool ValidateServerCertificate (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return ((sslPolicyErrors == SslPolicyErrors.None) ? true : false);
}
public static bool Auth (string server, string user, string pass)
{
// Получаем host и port
string[] hostport = server.Split (':');
string host = hostport[0];
int port = 110;
try {
port = System.Convert.ToInt32 (hostport[1]);
} catch {
// port = 110 by default
}
// Собственно аутентификация
if (port == 995) {
// Соединяемся
TcpClient client = new TcpClient (host, port);
// Открываем SSL stream
SslStream sslStream = new SslStream (client.GetStream (), false, new RemoteCertificateValidationCallback (ValidateServerCertificate), null);
// Настраиваем SSL stream
sslStream.ReadTimeout = readtimeout;
sslStream.WriteTimeout = writetimeout;
// SSL аутентификация
try {
sslStream.AuthenticateAsClient(host); // тут выдаёт "Couldn't complete EndRead"
} catch(Exception e) {
Log.Print("POP3AUTH:" + e.Message);
client.Close ();
throw new Exception ("E_POP3_CONNECT");
}
string request = "";
string response = "";
// Читаем "банер" сервера
response = ReadFrom (sslStream);
// Пишем USER
request = "USER " + user + "\r\n";
WriteTo (sslStream, request);
// Читаем ответ сервера
response = ReadFrom (sslStream);
if ((response.Length < 3) || (response.Substring (0, 3) != "+OK")) {
client.Close ();
throw new Exception ("E_POP3_LOGIN");
}
// Пишем PASS
request = "PASS " + pass + "\r\n";
WriteTo (sslStream, request);
// Читаем ответ сервера
response = ReadFrom (sslStream);
if ((response.Length < 3) || (response.Substring (0, 3) != "+OK")) {
client.Close ();
throw new Exception ("E_POP3_PASSWORD");
}
// Пишем QUIT
request = "QUIT\r\n";
WriteTo (sslStream, request);
// Читаем ответ сервера
response = ReadFrom (sslStream);
client.Close ();
} else if (port == 110) {
// Соединяемся
TcpClient client = new TcpClient (host, port);
// Открываем TCP stream
NetworkStream tcpStream = client.GetStream ();
// Настраиваем SSL stream
tcpStream.ReadTimeout = readtimeout;
tcpStream.WriteTimeout = writetimeout;
string request = "";
string response = "";
// Читаем "банер" сервера
response = ReadFrom (tcpStream);
// Пишем USER
request = "USER " + user + "\r\n";
WriteTo (tcpStream, request);
// Читаем ответ сервера
response = ReadFrom (tcpStream);
if ((response.Length < 3) || (response.Substring (0, 3) != "+OK")) {
client.Close ();
throw new Exception ("E_POP3_LOGIN");
}
// Пишем PASS
request = "PASS " + pass + "\r\n";
WriteTo (tcpStream, request);
// Читаем ответ сервера
response = ReadFrom (tcpStream);
if ((response.Length < 3) || (response.Substring (0, 3) != "+OK")) {
client.Close ();
throw new Exception ("E_POP3_PASSWORD");
}
// Пишем QUIT
request = "QUIT\r\n";
WriteTo (tcpStream, request);
// Читаем ответ сервера
response = ReadFrom (tcpStream);
client.Close ();
} else {
throw new Exception ("E_POP3_CONNECT");
}
return true;
}
static string ReadFrom (SslStream stream)
{
int bytes = -1;
StringBuilder result = new StringBuilder ();
byte[] buffer = new byte[2048];
do {
bytes = stream.Read (buffer, 0, buffer.Length);
// Декодируем и добавляем в основной буфер
Decoder decoder = Encoding.UTF8.GetDecoder ();
char[] chars = new char[decoder.GetCharCount (buffer, 0, bytes)];
decoder.GetChars (buffer, 0, bytes, chars, 0);
result.Append (chars);
// Проверка, что мы получили строчку
if (result.ToString ().IndexOf ("\r\n") != -1)
break;
} while (bytes != 0);
return result.ToString ();
}
static void WriteTo (SslStream stream, string message)
{
stream.Write (Encoding.UTF8.GetBytes (message));
stream.Flush ();
}
static string ReadFrom (NetworkStream stream)
{
int bytes = -1;
StringBuilder result = new StringBuilder ();
byte[] buffer = new byte[2048];
do {
bytes = stream.Read (buffer, 0, buffer.Length);
// Декодируем и добавляем в основной буфер
Decoder decoder = Encoding.UTF8.GetDecoder ();
char[] chars = new char[decoder.GetCharCount (buffer, 0, bytes)];
decoder.GetChars (buffer, 0, bytes, chars, 0);
result.Append (chars);
// Проверка, что мы получили строчку
if (result.ToString ().IndexOf ("\r\n") != -1)
break;
} while (bytes != 0);
return result.ToString ();
}
static void WriteTo (NetworkStream stream, string message)
{
byte[] buffer = Encoding.UTF8.GetBytes (message);
stream.Write (buffer, 0, buffer.Length);
stream.Flush ();
}
}
}
Изначально за основу взят пример:
http://msdn.microsoft.com/en-us/library/system.net.security.sslstream.aspx#Y5130
А вот под Mono при вызове sslStream.AuthenticateAsClient(host) выдаёт Exception: "Couldn't complete EndRead".
Я знаю, что EndRead — это асинхронный I/O.
Т.е. sslStream.AuthenticateAsClient() — всегда асинхронный?
Что это за зверь и как с ним бороться?
Есть ли у кого готовый пример, работающий под Mono?
P.S. Код является просто тестом, "написан на коленке", под .NET 100% работает, поэтому просьба не придираться особо по поводу "корявости"