关闭

关于SMTP那个程序的参考资料

504人阅读 评论(0) 收藏 举报

♦第一个参考资料是某牛人写的C的SMTP协议:

#include <stdio.h>

#include <stdlib.h>

#include <winsock2.h>

 

#pragma comment(lib, "ws2_32.lib")

 

const char *MailData = "From: "leo"<xxxxx@gmail.com> "

                        "Subject: It's a test mail! . ";

 

 

int main(int argc, char *argv[])

{

 

    WSADATA wsaData;

    WORD wVersionRequested = MAKEWORD(2, 2);

    struct hostent *pHostent = NULL;

    SOCKET server = INVALID_SOCKET;

    struct sockaddr_in service;

    int retConnect = 0;

    char Buffer[1024] = {0};

 

    if(WSAStartup(wVersionRequested, &wsaData) != 0){

        printf("Error at WSAStartup() ");

        goto WSACleanup;

    }

    

    server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);        //Create a Socket

 

    if(server == INVALID_SOCKET){

        printf("Error at socket(): %ld ", WSAGetLastError());

        goto WSACleanup;

    }

    

    pHostent = gethostbyname("smtp.126.com");       //Get the Mail Server name

    if(pHostent == NULL){

        printf("The Host Name is Invalid... ");

        goto WSACleanup;

    }

    

    service.sin_family = AF_INET;

    memcpy(&service.sin_addr.s_addr, pHostent->h_addr_list[0],

pHostent->h_length);

    service.sin_port = htons(25);

 

    //Connect to the remote Mail Server

    retConnect = connect(server, (struct sockaddr*)&service, sizeof(service));

    if(retConnect == SOCKET_ERROR){

        printf("Failed to connect. ");

        goto WSACleanup;

    }

    

    printf("Connect to %s.... ", inet_ntoa(service.sin_addr));

    

    //Receive Data From the Mail Server

    ZeroMemory(Buffer, sizeof(Buffer));

    retConnect = recv(server, Buffer, sizeof(Buffer), 0);

    if(retConnect == SOCKET_ERROR){

        printf("Failed to connect. ");    

        goto WSACleanup;     

    }else{

        printf("%s ", Buffer);

    }

    

    //Send "HELO Server.... " to the Mail Server

    retConnect = send(server, "HELO Server.... ", strlen("HELO

Server.... "), 0);

    if(retConnect == SOCKET_ERROR){

        printf("Send HELO to the Mail Failure. ");

        goto WSACleanup;

    }else{

       printf("HELO Server.... ");   

    }  

    

    //Receive Data From the Mail Server

    ZeroMemory(Buffer, sizeof(Buffer));

    retConnect = recv(server, Buffer, sizeof(Buffer), 0);

    if(retConnect == SOCKET_ERROR){

        printf("Failed to connect. ");    

        goto WSACleanup;     

    }else{

        printf("%s ", Buffer);

    }

 

    //Send "AUTH LOGIN " to the Mail Server

    retConnect = send(server, "AUTH LOGIN ", strlen("AUTH LOGIN "), 0);

    if(retConnect == SOCKET_ERROR){

        printf("Send "AUTH LOGIN" to Mail Failure. ");

        goto WSACleanup;

    }else{

        printf("AUTH LOGIN ");   

    }      

 

    //Receive Data From the Mail Server

    ZeroMemory(Buffer, sizeof(Buffer));

    retConnect = recv(server, Buffer, sizeof(Buffer), 0);

    if(retConnect == SOCKET_ERROR){

        printf("Receive Data From Mail Server Failure. ");    

        goto WSACleanup;     

    }else{ 

        printf("%s ", Buffer);

    }

  

    //Send UserName to the Mail Server. The UserName is Encoded by Base64.

    retConnect = send(server, "bGJleW9uZDRrb21h ",

strlen("bGJleW9uZDRrb21h "), 0);

    if(retConnect == SOCKET_ERROR){

        printf("Send UserName to the Mail Failure. ");

        goto WSACleanup;

    }else{

        printf("UserName ");   

    }      

        

    //Receive Data From the Mail Server

    ZeroMemory(Buffer, sizeof(Buffer));

    retConnect = recv(server, Buffer, sizeof(Buffer), 0);

    if(retConnect == SOCKET_ERROR){

        printf("Receive Data From the Mail Server Failure. ");    

        goto WSACleanup;     

    }else{

        printf("%s ", Buffer);

    }

 

    //Send Password to the Mail Server The Password is Encoded by Base64.

    retConnect = send(server, "bGJleW9uZDRrb21h ",

strlen("bGJleW9uZDRrb21h "), 0);

    if(retConnect == SOCKET_ERROR){

        printf("Send Password to Mail Failure. ");

        goto WSACleanup;

    }else{

        printf("Password ");   

    }  

    

    //Receive Data From the Mail Server

    ZeroMemory(Buffer, sizeof(Buffer));

    retConnect = recv(server, Buffer, sizeof(Buffer), 0);

    if(retConnect == SOCKET_ERROR){

        printf("Receive Data From the Mail Server Failure. ");    

        goto WSACleanup;     

    }else{

        printf("%s ", Buffer);

    }

 

    //Send "Mail From: " File to the Mail Server, sender's Mail Address

    retConnect = send(server, "MAIL FROM: <lbeyond4koma@126.com> ",

strlen("MAIL FROM: <lbeyond4koma@126.com> "), 0);

    if(retConnect == SOCKET_ERROR){

        printf("Send "Mail From: " to Mail Failure. ");

        goto WSACleanup;

    }else{

        printf("MAIL FROM: <lbeyond4koma@126.com> ");   

    }  

    

    //Receive Data From the Mail Server

    ZeroMemory(Buffer, sizeof(Buffer));

    retConnect = recv(server, Buffer, sizeof(Buffer), 0);

    if(retConnect == SOCKET_ERROR){

        printf("Receive Data From Mail Server Failure. ");    

        goto WSACleanup;     

    }else{

        Buffer[retConnect] = ' ';

        printf("%s ", Buffer);

    }

 

    //Send "RCPT TO: " File to the Mail Server, receiver 's Mail Address

    retConnect = send(server, "RCPT TO: <dl88250@gmail.com> ", strlen("RCPT

TO: <dl88250@gmail.com> "), 0);

    if(retConnect == SOCKET_ERROR){

        printf("Send "RCPT TO: " to Mail Failure. ");

        goto WSACleanup;

    }else{

        printf("RCPT TO: <dl88250@gmail.com> ");   

    }  

    

    //Receive Data From the Mail Server

    ZeroMemory(Buffer, sizeof(Buffer));

    retConnect = recv(server, Buffer, sizeof(Buffer), 0);

    if(retConnect == SOCKET_ERROR){

        printf("Receive Data From the Mail Server Failure. ");    

        goto WSACleanup;     

    }else{

        printf("%s ", Buffer);

    }

 

    //Send "Data" Fiele to the Mail Server, start to Send mail

    retConnect = send(server, "Data ", strlen("Data "), 0);

    if(retConnect == SOCKET_ERROR){

        printf("Send "Data" Field to Mail Failure... ");

        goto WSACleanup;

    }else{

        printf("Data ");   

    }  

 

    //Receive Data From the Mail Server

    ZeroMemory(Buffer, sizeof(Buffer));

    retConnect = recv(server, Buffer, sizeof(Buffer), 0);

    if(retConnect == SOCKET_ERROR){

        printf("Receive Data From Mail Server Failure... ");    

        goto WSACleanup;     

    }else{

        printf("%s ", Buffer);

    }

 

    //Send Mail data to the the Mail Server

    retConnect = send(server, MailData, strlen(MailData), 0);

    if(retConnect == SOCKET_ERROR){

        printf("Send Context Of Mail to Mail Failure... ");

        goto WSACleanup;

    }else{

        printf("%s ", MailData);

    }

 

    //Receive Data From the Mail Server

    ZeroMemory(Buffer, sizeof(Buffer));

    retConnect = recv(server, Buffer, sizeof(Buffer), 0);

    if(retConnect == SOCKET_ERROR){

        printf("Receive Data From Mail Server Failure... ");    

        goto WSACleanup;     

    }else{

        printf("%s ", Buffer);

    }

 

    //Send "QUIT" Context to the Mail Server

    retConnect = send(server, "QUIT ", strlen("QUIT "), 0);

    if(retConnect == SOCKET_ERROR){

        printf("Send "Quit" to Mail Failure... ");

        goto WSACleanup;

    }else{

        printf("Quit ");   

    }  

   

    printf("Send Mail Successful! ");

              

    WSACleanup:{

        if(server != INVALID_SOCKET){

            closesocket(server);

        }

        WSACleanup();        

    }

    system("pause");

    return 0;

}

♦第二个是VC的

void CMailerDlg::OnOk()
{
 UpdateData(TRUE);

 CString Serv = "EHLO"+m_Server+"/r/n";

 CString From = "MAIL FROM :<"+m_From+">/r/n";

  CString To = "RCPT TO :<"+m_To+">/r/n";

   CString Text = m_Message+"/r/n/r/n./r/n";

   char *MailMessage[]=
 {
    Serv.GetBuffer(1),
     From.GetBuffer(1),
     To.GetBuffer(1),
     "DATA/r/n",
     Text.GetBuffer(1),
     "QUIT/r/n",
     NULL
   };

    WSADATA Wsa;

    WSAStartup(0x0101,&Wsa);

    SOCKET s = socket(AF_INET,SOCK_STREAM,0);

    SOCKADDR_IN sin;
    sin.sin_addr.s_addr=inet_addr(m_Server);

    sin.sin_family=AF_INET;
    sin.sin_port=htons(25);

    if(connect(s,(LPSOCKADDR)&sin,sizeof(sin))==SOCKET_ERROR)
     MessageBox("Error:can't connect to server:(","Error",MB_OK|MB_ICONERROR);

    int iLength=0;
    int iEnd=0;
    char sBuff[255]="";
    int iMsg=0;

    do{
     iLength=recv(s,(LPSTR)sBuff+iEnd,sizeof(sBuff)-iEnd,0);
     iEnd+=iLength;
     sBuff[iEnd]='/0';

     send(s,(LPSTR)MailMessage[iMsg],strlen(MailMessage[iMsg]),0);
     iMsg++;

    }while(MailMessage[iMsg]);

    closesocket(s);
    WSACleanup();

}

♦关于SMTP的理论部分文档

SMTP网络协议编程实现文档

SMTP协议简介

 

最常用的网络服务之一是电子邮件(E-mail) 电子邮件用于把包含文本、视频或图片的单条报文发送给一个或者多个收件人。 简单邮件传输协议(Simple Mail Transfer Protocol,SMTP)是在因特网中用于电子邮件的标准机制。 SMTP通过使用发送方SMTP和接收方SMTP进程来发送和接收E-mail消息,这些进程执行E-mail的传送和接收服务。 SMTP服务器监听TCP 25号端口,并对客户端发送的合法命令做出响应。

 

邮件传输过程

 

从发件人到收件人之间的邮件传输过程由三个阶段构成:
     ·
第一阶段:电子邮件从用户代理进入本地服务器。邮件并不是直接传送到远程服务器中的,因为远程服务器不能保证始终可用。 所以,邮件在发送前会一直保存在本地服务器中。用户代理使用SMTP客户端软件,而本地服务器使用SMTP服务器软件。

     ·
第二阶段:电子邮件由本地服务器中继传递。 在这一阶段,远程服务器作为SMTP服务器,而本地服务器作为SMTP客户端。 电子邮件分发给远程服务器,而不是远程用户代理。 原因是SMTP报文必须由始终处于运行状态的服务器接收,服务器必须不间断运行的原因是由于邮件可能随时到达。 然而,人们经常在一天的工作结束时关闭计算机, 所以,通常情况下,组织机构会分派一台计算机作为邮件服务器,运行SMTP服务器程序。 电子邮件由这台邮件服务器接收,存储在用户的邮箱中,便于以后检索。
     ·
第三阶段:远程用户代理使用邮件访问协议,如POP3或者IMAP,来访问邮箱并获取邮件。

 

SMTP标准及扩展命令

 

SMTP命令

作 用

HELO

用于开始会话,通常在HELO后跟客户机的主机名(hostname)

MAIL

用于指出发起会话的发件人,通常在MAIL后面跟From参数来指定发件人

RCPT

用于指定该消息的接收人,通常在RCPT后面跟To参数来指定收件人

DATA

表示客户端开始向服务器端发送消息(邮件) 正文

RSET

放弃当前的数据传输

VRFY

用以确定指定的收件人在服务器端是合法的(在发送邮件正文之前确定邮箱存在), 考虑到这个功能有一定的安全隐患,此命令在Exchange中不被支持,SMTP服务器总是返回非法地址

TURN

交换客户端和服务器角色,允许客户端触发服务器端的邮件传送(此命令也很少被使用)

EHLO(扩展)

作为标准HELO的替代者,客户端发送EHLO来跟服务器确定其对ESMTP的支持程度, 服务器会返回一个它所支持的ESMTP命令字列表给客户端。

ATRN(扩展)

TURN命令的增强,在启用TURN之前需要身份认证。

ETRN(扩展)

TURN命令的增强,功能上与TURN类似,但是通过创建另外一个独立的会话完成TURN报文传送。

Pipelining(扩展)

允许SMTP客户端在服务器响应之前以异步的方式连续发送若干的命令字。 例如,可以在获得服务器确认之前,连续发送多个RCPT命令字,这样可以实现在慢速网络上的高效通信。

BDAT(扩展)

此命令字替代DATA,允许客户端采用批量的方式传送消息报文,可以在一定程度上降低接收方的负载。

AUTH(扩展)

允许客户端使用基本验证、Windows集成验证(NTLMKerberos)与服务器进行身份认证, 进行身份认证也是避免SMTP服务器被relay的重要手段。

STARTTLS(扩展)

用来表示客户端希望能够与服务器建立一个基于TLS的加密会话。

XEXCH50(扩展)

用以传送Exchange服务器间专用的报头数据。

 

 

SMTP会话过程

 

SMTPTCP协议25号端口监听连接请求,建立TCP连接后,客户端发送HELO命令以标识发件人自己的身份。 然后客户端发送MAIL命令服务器端正希望以OK作为响应,表明准备接收。 客户端发送RCPT命令,以标识该电子邮件的计划接收人,可以有多个RCPT行,服务器端则表示是否愿意为收件人接受邮件。 协商结束,发送邮件,用命令DATA发送,.表示结束输入内容一起发送出去。 结束此次发送,QUIT命令退出。

其实这个才是最强的,他把附件啊,什么的都实现了的,但是他不是用SOCKET来做的,是调用本地邮件客户端来做的

/////////////////////////////////////////////////////////////////////////////
// CSendEMailDlg message handlers

BOOL CSendEMailDlg::OnInitDialog()
{
 CDialog::OnInitDialog();

 // Add "About..." menu item to system menu.

 // IDM_ABOUTBOX must be in the system command range.
 ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
 ASSERT(IDM_ABOUTBOX < 0xF000);

 CMenu* pSysMenu = GetSystemMenu(FALSE);
 if (pSysMenu != NULL)
 {
  CString strAboutMenu;
  strAboutMenu.LoadString(IDS_ABOUTBOX);
  if (!strAboutMenu.IsEmpty())
  {
   pSysMenu->AppendMenu(MF_SEPARATOR);
   pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
  }
 }

 // Set the icon for this dialog.  The framework does this automatically
 //  when the application's main window is not a dialog
 SetIcon(m_hIcon, TRUE);   // Set big icon
 SetIcon(m_hIcon, FALSE);  // Set small icon
 
 //获取当前路径
 CString szPath("");
 GetModuleFileName(NULL,szPath.GetBuffer(MAX_PATH),MAX_PATH); 
 szPath.ReleaseBuffer();
 szPath = szPath.Left(szPath.ReverseFind('//') + 1);

 //从readme文件中读取邮件的正文内容
 CString szFileName = szPath + "//Readme.txt";
 CFileFind find;
 bool bRet = find.FindFile(szFileName);
 find.Close();
 if(!bRet)
 {
  HRSRC hSrc = FindResource(NULL,MAKEINTRESOURCE(IDR_README),_T("OWNER_DATA"));
  if(hSrc != NULL)
  {
   HGLOBAL hGlobal = LoadResource(NULL,hSrc);
   if(hGlobal != NULL)
   {
    LPVOID lp = LockResource(hGlobal);
    DWORD dwSize = SizeofResource(NULL,hSrc);

    CFile file;
    if(file.Open(szFileName,CFile::modeCreate|CFile::modeWrite))
    {
     file.Write(lp,dwSize);
     file.Close();
    }
    FreeResource(hGlobal);
   }
  }
 } 

 CFile file;
 if(file.Open(szFileName,CFile::modeRead))
 {
  DWORD dwLen = file.GetLength();
  m_szText.ReleaseBuffer();
  file.Read(m_szText.GetBuffer(dwLen),dwLen);
  file.Close();

  m_szText.ReleaseBuffer();
  UpdateData(false);
 }

 m_list.AddString(szFileName);
 
 return TRUE;  // return TRUE  unless you set the focus to a control
}

void CSendEMailDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
 if ((nID & 0xFFF0) == IDM_ABOUTBOX)
 {
  CAboutDlg dlgAbout;
  dlgAbout.DoModal();
 }
 else
 {
  CDialog::OnSysCommand(nID, lParam);
 }
}

void CSendEMailDlg::OnPaint()
{
 if (IsIconic())
 {
  CPaintDC dc(this); // device context for painting

  SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

  // Center icon in client rectangle
  int cxIcon = GetSystemMetrics(SM_CXICON);
  int cyIcon = GetSystemMetrics(SM_CYICON);
  CRect rect;
  GetClientRect(&rect);
  int x = (rect.Width() - cxIcon + 1) / 2;
  int y = (rect.Height() - cyIcon + 1) / 2;

  // Draw the icon
  dc.DrawIcon(x, y, m_hIcon);
 }
 else
 {
  CDialog::OnPaint();
 }
}

// The system calls this to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CSendEMailDlg::OnQueryDragIcon()
{
 return (HCURSOR) m_hIcon;
}

//DEL void CSendEMailDlg::OnShellExecute()
//DEL {
//DEL /* UpdateData(true);
//DEL  
//DEL  if(m_szEmail.IsEmpty()) m_szEmail = m_szDefaultEMail;
//DEL  m_szEmail = "mailto:" + m_szEmail;
//DEL
//DEL  ShellExecute(NULL,NULL,m_szEmail,NULL,NULL,SW_SHOW);*/
//DEL }

/*********************************************************************
 * 函数名称:CSendEMailDlg::OnAddAttachment
 * 说明:  添加附件到列表框中
 
*********************************************************************/
//DEL void CSendEMailDlg::OnAddAttachment()
//DEL {
//DEL  CFileDialog dlg(true,NULL,NULL,OFN_ALLOWMULTISELECT);
//DEL
//DEL  if(dlg.DoModal() == IDOK)
//DEL  {
//DEL   POSITION pos = dlg.GetStartPosition();
//DEL   while(pos != NULL)
//DEL   {
//DEL    m_list.AddString(dlg.GetNextPathName(pos));
//DEL   }
//DEL  }
//DEL }


/*********************************************************************
 * 函数名称:CSendEMailDlg::OnSendMapi
 * 说明:  调用MAPI函数发送邮件。摘自 VC98/MFC/SRC/DOCMAPI.CPP
 
*********************************************************************/
void CSendEMailDlg::OnSendMapi()
{
 UpdateData(true);

 //加载MAPI32.DLL动态库
 HMODULE hMod = LoadLibrary("MAPI32.DLL");

 if (hMod == NULL) //加载动态库失败
 {
  AfxMessageBox(AFX_IDP_FAILED_MAPI_LOAD);
  return;
 }

 //获取发送邮件的函数地址
 ULONG (PASCAL *lpfnSendMail)(ULONG, ULONG, MapiMessage*, FLAGS, ULONG);
 (FARPROC&)lpfnSendMail = GetProcAddress(hMod, "MAPISendMail");

 if (lpfnSendMail == NULL)
 {
  AfxMessageBox(AFX_IDP_INVALID_MAPI_DLL);
  return;
 }

 int nFileCount = m_list.GetCount(); //有多少个附件需要发送

 //分配内存保存附件信息 不能使用静态数组,因为不知道要发送附件的个数
 MapiFileDesc* pFileDesc = (MapiFileDesc*)malloc(sizeof(MapiFileDesc) * nFileCount);
 memset(pFileDesc,0,sizeof(MapiFileDesc) * nFileCount);

 //分配内存保存附件文件路径
 TCHAR* pTchPath = (TCHAR*)malloc(MAX_PATH * nFileCount);

 CString szText;
 for(int i = 0;i < nFileCount;i++)
 {
  TCHAR* p = pTchPath + MAX_PATH * i;
  m_list.GetText(i,szText);
  strcpy(p,szText);

  (pFileDesc + i)->nPosition = (ULONG)-1;
  (pFileDesc + i)->lpszPathName = p;
  (pFileDesc + i)->lpszFileName = p;
 }

 //收件人结构信息
 MapiRecipDesc recip;
 memset(&recip,0,sizeof(MapiRecipDesc));
 recip.lpszAddress = m_szEmailMAPI.GetBuffer(0);
 recip.ulRecipClass = MAPI_TO;

 //邮件结构信息
 MapiMessage message;
 memset(&message, 0, sizeof(message));
 message.nFileCount = nFileCount;    //文件个数
 message.lpFiles  = pFileDesc;    //文件信息
 message.nRecipCount = 1;      //收件人个数
 message.lpRecips  = &recip;     //收件人
 message.lpszSubject = m_szSubject.GetBuffer(0); //主题
 m_szText.ReleaseBuffer();
 message.lpszNoteText= m_szText.GetBuffer(0); //正文内容

 //保存本程序窗口指针,因为发完邮件后要返回本程序的窗口
 CWnd* pParentWnd = CWnd::GetSafeOwner(NULL, NULL);

 //发送邮件
 int nError =
  (0, 0,
     &message, MAPI_LOGON_UI|MAPI_DIALOG, 0);

 if (nError != SUCCESS_SUCCESS && nError != MAPI_USER_ABORT
   && nError != MAPI_E_LOGIN_FAILURE)
 {
  AfxMessageBox(AFX_IDP_FAILED_MAPI_SEND);
 }

 //返回程序
 pParentWnd->SetActiveWindow();

 //不要忘了释放分配的内存
 free(pFileDesc);
 free(pTchPath);
 FreeLibrary(hMod);
}

void CSendEMailDlg::OnAbout()
{
 CAboutDlg dlg;
 dlg.DoModal();
}

void CSendEMailDlg::OnChangeEmail()
{
 // TODO: If this is a RICHEDIT control, the control will not
 // send this notification unless you override the CDialog::OnInitDialog()
 // function and call CRichEditCtrl().SetEventMask()
 // with the ENM_CHANGE flag ORed into the mask.
 
 // TODO: Add your control notification handler code here
 
}

void CSendEMailDlg::OnChangeEmail2()
{
 // TODO: If this is a RICHEDIT control, the control will not
 // send this notification unless you override the CDialog::OnInitDialog()
 // function and call CRichEditCtrl().SetEventMask()
 // with the ENM_CHANGE flag ORed into the mask.
 
 // TODO: Add your control notification handler code here
 
}

void CSendEMailDlg::OnOK()
{
 // TODO: Add extra validation here
 
 CDialog::OnOK();
}

void CSendEMailDlg::OnChangeText()
{
 // TODO: If this is a RICHEDIT control, the control will not
 // send this notification unless you override the CDialog::OnInitDialog()
 // function and call CRichEditCtrl().SetEventMask()
 // with the ENM_CHANGE flag ORed into the mask.
 
 // TODO: Add your control notification handler code here
 
}

 

0
0
猜你在找
【套餐】Hadoop生态系统零基础入门
【套餐】嵌入式Linux C编程基础
【套餐】2017软考系统集成项目——任铄
【套餐】Android 5.x顶级视频课程——李宁
【套餐】深度学习入门视频课程——唐宇迪
【直播】广义线性模型及其应用——李科
【直播】从0到1 区块链的概念到实践
【直播】计算机视觉原理及实战——屈教授
【直播】机器学习之凸优化——马博士
【直播】机器学习&数据挖掘7周实训--韦玮
查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:2054次
    • 积分:53
    • 等级:
    • 排名:千里之外
    • 原创:3篇
    • 转载:1篇
    • 译文:0篇
    • 评论:0条
    文章存档