我正在尝试使用c#中的TcpClient发送电子邮件。 我不知道这是否可能。
*******我知道我可以使用SmtpClient,但这是一个功课,我只需要使用套接字******
我写了这段代码:
TcpClient tcpclient = new TcpClient();
// HOST NAME POP SERVER and gmail uses port number 995 for POP
//tcpclient.Connect("pop.gmail.com", 995);
tcpclient.Connect("smtp.gmail.com", 465);
// This is Secure Stream // opened the connection between client and POP Server
System.Net.Security.SslStream sslstream = new SslStream(tcpclient.GetStream());
// authenticate as client
//sslstream.AuthenticateAsClient("pop.gmail.com");
sslstream.AuthenticateAsClient("smtp.gmail.com");
//bool flag = sslstream.IsAuthenticated; // check flag
// Asssigned the writer to stream
System.IO.StreamWriter sw = new StreamWriter(sslstream);
// Assigned reader to stream
System.IO.StreamReader reader = new StreamReader(sslstream);
// refer POP rfc command, there very few around 6-9 command
sw.WriteLine("EHLO " + "smtp.gmail.com");
sw.Flush();
sw.WriteLine("AUTH LOGIN/r/n");
sw.Flush();
sw.WriteLine("******@gmail.com/r/n");
sw.Flush();
// sent to server
sw.WriteLine("***********/r/n");
sw.Flush();
//// this will retrive your first email
//sw.WriteLine("RETR 1");
//sw.Flush();
//// close the connection
//sw.WriteLine("Quit ");
//sw.Flush();
sw.WriteLine("MAIL FROM:<" + "******@gmail.com" + ">\r\n");
sw.Flush();
sw.WriteLine("RCPT TO:<" + "*******@***.com" + ">\r\n");
sw.Flush();
sw.WriteLine("DATA\r\n");
sw.Flush();
sw.WriteLine("Subject: Email test\r\n");
sw.Flush();
sw.WriteLine("Test 1 2 3\r\n");
sw.Flush();
sw.WriteLine(".\r\n");
sw.Flush();
sw.WriteLine("QUIT\r\n");
sw.Flush();
string str = string.Empty;
string strTemp = string.Empty;
while ((strTemp = reader.ReadLine()) != null)
{
// find the . character in line
if (strTemp == ".")
{
break;
}
if (strTemp.IndexOf("-ERR") != -1)
{
break;
}
str += strTemp;
}
}
读者得到的信息是:
"250-smtp.gmail.com at your service, [151.238.124.27]\r\n250-SIZE 35882577\r\n250-8BITMIME\r\n250-AUTH LOGIN PLAIN XOAUTH2 PLAIN-CLIENTTOKEN OAUTHBEARER XOAUTH\r\n250-ENHANCEDSTATUSCODES\r\n250-PIPELINING\r\n250-CHUNKING\r\n250 SMTPUTF8\r\n451 4.5.0 SMTP protocol violation, see RFC 2821 x17-v6sm5346253edx.53 - gsmtp\r\n"
知道哪个部分是错的,所以我可以通过TcpClient发送电子邮件?
如果不能发生这种情况,我该如何使用套接字发送电子邮件?
答案 0 :(得分:2)
问题是您未正确遵循SMTP协议,如RFC 5321,RFC 2920和RFC 2554中所述。
您在回复AUTH LOGIN
命令时从服务器收到的错误消息很清楚:
451 4.5.0 SMTP协议违规,请参阅RFC 2821 x17-v6sm5346253edx.53 - gsmtp
具体地,
/r/n
正在终止您的一些命令,这两个命令都是错误的(应该是\r\n
)并且是多余的(因为您使用WriteLine()
,它发送\r\n
1}}为你)。
您正在发送一堆SMTP命令,而不会读取每个命令之间的任何响应。这称为Command Piplining。但是,您没有检查服务器的EHLO
响应,以确保服务器甚至允许管道传输。除非服务器首先告诉您它是正常的,否则您无法管道命令。
您没有正确阅读回复。无论您是否使用管道传输,SMTP响应都采用特定格式,如RFC 5321 Section 4.2中所述。您的阅读代码不符合该格式,甚至没有关闭。
您未正确使用SMTP服务器进行身份验证。特别是,您发送到服务器的值必须以UTF-8和base64编码。您需要注意服务器的提示,以了解何时发送用户名以及何时发送密码。有些服务器不需要这两个值。
话虽如此,请尝试更像这样的事情:
private System.IO.StreamReader reader;
private System.IO.StreamWriter writer;
public class SmtpCmdFailedException : Exception
{
public int ReplyCode;
public SmtpCmdFailedException(int code, string message)
: base(message)
{
ReplyCode = code;
}
}
private int readResponse(ref string replyText, params int[] expectedReplyCodes)
{
string line = reader.ReadLine();
if (line == null)
throw new EndOfStreamException();
// extract the 3-digit reply code
string replyCodeStr = line.Substring(0, 3);
// extract the text message, if any
replyText = line.Substring(4);
// check for a multi-line response
if ((line.Length > 3) && (line[3] == '-'))
{
// keep reading until the final line is received
string contStr = replyCodeStr + "-";
do
{
line = reader.ReadLine();
if (line == null)
throw new EndOfStreamException();
replyText += "\n" + line.Substring(4);
}
while (line.StartsWith(contStr));
}
int replyCode = Int32.Parse(replyCodeStr);
// if the caller expects specific reply code(s), check
// for a match and throw an exception if not found...
if (expectedReplyCodes.Length > 0)
{
if (Array.IndexOf(expectedReplyCodes, replyCode) == -1)
throw new SmtpCmdFailedException(replyCode, replyText);
}
// return the actual reply code that was received
return replyCode;
}
private int readResponse(params int[] expectedReplyCodes)
{
string ignored;
return readResponse(ignored, expectedReplyCodes);
}
private int sendCommand(string command, ref string replyText, params int[] expectedReplyCodes)
{
writer.WriteLine(command);
writer.Flush();
return readResponse(replyText, expectedReplyCodes);
}
private int sendCommand(string command, params int[] expectedReplyCodes)
{
string ignored;
return sendCommand(command, ignored, expectedReplyCodes);
}
void doAuthLogin(string username, string password)
{
// an authentication command returns 235 if authentication
// is finished successfully, or 334 to prompt for more data.
// Anything else is an error...
string replyText;
int replyCode = sendCommand("AUTH LOGIN", replyText, 235, 334);
if (replyCode == 334)
{
// in the original spec for LOGIN (draft-murchison-sasl-login-00.txt), the
// username prompt is defined as 'User Name' and the password prompt is
// defined as 'Password'. However, the spec also mentions that there is at
// least one widely deployed client that expects 'Username:' and 'Password:'
// instead, and those are the prompts that most 3rd party documentations
// of LOGIN describe. So we will look for all known prompts and act accordingly.
// Also throwing in 'Username' just for good measure, as that one has been seen
// in the wild, too...
string[] challenges = new string[]{"Username:", "User Name", "Username", "Password:", "Password"};
do
{
string challenge = Encoding.UTF8.GetString(Convert.FromBase64String(replyText));
switch (Array.IndexOf(challenges, challenge))
{
case 0:
case 1:
case 2:
replyCode = sendCommand(Convert.ToBase64String(Encoding.UTF8.GetBytes(username)), replyText, 235, 334);
break;
case 3:
case 4:
replyCode = sendCommand(Convert.ToBase64String(Encoding.UTF8.GetBytes(password)), replyText, 235, 334);
break;
default:
throw new SmtpCmdFailedException(replyCode, replyText);
}
}
while (replyCode == 334);
}
}
...
TcpClient tcpclient = new TcpClient();
tcpclient.Connect("smtp.gmail.com", 465);
// implicit SSL is always used on SMTP port 465
System.Net.Security.SslStream sslstream = new SslStream(tcpclient.GetStream());
sslstream.AuthenticateAsClient("smtp.gmail.com");
//bool flag = sslstream.IsAuthenticated; // check flag
writer = new StreamWriter(sslstream);
reader = new StreamReader(sslstream);
string replyText;
string[] capabilities = null;
string[] authTypes = null;
// read the server's initial greeting
readResponse(220);
// identify myself and get the server's capabilities
if (sendCommand("EHLO myClientName", replyText) == 250)
{
// parse capabilities
capabilities = replyText.Split(new Char[]{'\n'});
string auth = Array.Find(capabilities, s => s.StartsWith("AUTH ", true, null));
authTypes = auth.Substring(5).Split(new Char[]{' '});
// authenticate as needed...
if (Array.IndexOf(authTypes, "LOGIN") != -1)
doAuthLogin("******@gmail.com", "***********");
}
else
{
// EHLO not supported, have to use HELO instead, but then
// the server's capabilities are unknown...
capabilities = new string[]{};
authTypes = new string[]{};
sendCommand("HELO myclientname", 250);
// try to authenticate anyway...
doAuthLogin("******@gmail.com", "***********");
}
// check for pipelining support... (OPTIONAL!!!)
if (Array.IndexOf(capabilities, "PIPELINING") != -1)
{
// can pipeline...
// send all commands first without reading responses in between
writer.WriteLine("MAIL FROM:<" + "******@gmail.com" + ">");
writer.WriteLine("RCPT TO:<" + "*******@***.com" + ">");
writer.WriteLine("DATA");
writer.Flush();
// now read the responses...
Exception e = null;
// MAIL FROM
int replyCode = readResponse(replyText);
if (replyCode != 250)
e = new SmtpCmdFailedException(replyCode, replyText);
// RCPT TO
replyCode = readResponse(replyText);
if ((replyCode != 250) && (replyCode != 251) && (e == null))
e = new SmtpCmdFailedException(replyCode, replyText);
// DATA
replyCode = readResponse(replyText);
if (replyCode == 354)
{
// DATA accepted, must send email followed by "."
writer.WriteLine("Subject: Email test");
writer.WriteLine("Test 1 2 3");
writer.WriteLine(".");
writer.Flush();
// read the response
replyCode = readResponse(replyText);
if ((replyCode != 250) && (e == null))
e = new SmtpCmdFailedException(replyCode, replyText);
}
else
{
// DATA rejected, do not send email
if (e == null)
e = new SmtpCmdFailedException(replyCode, replyText);
}
if (e != null)
{
// if any command failed, reset the session
sendCommand("RSET");
throw e;
}
}
else
{
// not pipelining, MUST read each response before sending the next command...
sendCommand("MAIL FROM:<" + "******@gmail.com" + ">", 250);
try
{
sendCommand("RCPT TO:<" + "*******@***.com" + ">", 250, 251);
sendCommand("DATA", 354);
writer.WriteLine("Subject: Email test");
writer.WriteLine("");
writer.WriteLine("Test 1 2 3");
writer.Flush();
sendCommand(".", 250);
}
catch (SmtpCmdFailedException e)
{
// if any command failed, reset the session
sendCommand("RSET");
throw;
}
}
// all done
sendCommand("QUIT", 221);