我想知道您是否可以帮助我了解这里出了什么问题。我正在尝试编写一个在端口25上进行SMTP对话的小客户端。

如果您回想起SMTP,则需要发送一些信息,然后在DATA消息之后编写电子邮件,并在发送电子邮件的一行上以句点结尾。

程序处理此问题的方式存在问题。它可以很好地处理对话,直到DATA消息之后。仅当我先输入时,它才能识别该时间段。在任何后续行之后,所有代码执行似乎都丢失了。 if语句无法识别是否输入了句点。再次感谢你。随附相关代码。

void readstuff(int sock, char* buf) {
    int r = read (sock, buf, BUFSIZE -1);
    buf[r] = NULL;
    cout << buf << endl;
}

void doit(int sock, string arg, char* buf) {
    int r = write(sock, arg.c_str(), arg.length());
    readstuff(sock, buf);
}

int main(int argc, char *argv[]) {
    char buf[BUFSIZE];

    // Make a socket
    int sock = MakeSocket(argv[1], argv[2]);
    cout << "socket is " << sock << endl;
    assert(sock != -1);

    // Begin dialogue
    doit(sock, "HELO " + org.substr(org.find("@") + 1) + "\r\n", buf);
    doit(sock, "MAIL FROM: <" + org + "> \r\n", buf);
    doit(sock, "RCPT TO: <" + dest + "> \r\n", buf);
    doit(sock, "DATA \r\n", buf);
    readstuff(sock, buf);  //should say "go ahead"

    //User writes email here
    while (true) {
        string line = "";
        getline(cin, line);
        doit(sock, line + "\r\n", buf);
        if (line == ".") {
            readstuff(sock, buf); //should say "email cleared to send"
            return 0;
        }
    }
}

最佳答案

请阅读SMTP规范RFC 5321,尤其是4.1.1.4 DATA4.5.2 Transparency部分:

4.1.1.4.  DATA (DATA)

   The receiver normally sends a 354 response to DATA, and then treats
   the lines (strings ending in <CRLF> sequences, as described in
   Section 2.3.7) following the command as mail data from the sender.
   This command causes the mail data to be appended to the mail data
   buffer.  The mail data may contain any of the 128 ASCII character
   codes, although experience has indicated that use of control
   characters other than SP, HT, CR, and LF may cause problems and
   SHOULD be avoided when possible.

   The mail data are terminated by a line containing only a period, that
   is, the character sequence "<CRLF>.<CRLF>", where the first <CRLF> is
   actually the terminator of the previous line (see Section 4.5.2).
   This is the end of mail data indication.  The first <CRLF> of this
   terminating sequence is also the <CRLF> that ends the final line of
   the data (message text) or, if there was no mail data, ends the DATA
   command itself (the "no mail data" case does not conform to this
   specification since it would require that neither the trace header
   fields required by this specification nor the message header section
   required by RFC 5322 [4] be transmitted).  An extra <CRLF> MUST NOT
   be added, as that would cause an empty line to be added to the
   message.  The only exception to this rule would arise if the message
   body were passed to the originating SMTP-sender with a final "line"
   that did not end in <CRLF>; in that case, the originating SMTP system
   MUST either reject the message as invalid or add <CRLF> in order to
   have the receiving SMTP server recognize the "end of data" condition.

   The custom of accepting lines ending only in <LF>, as a concession to
   non-conforming behavior on the part of some UNIX systems, has proven
   to cause more interoperability problems than it solves, and SMTP
   server systems MUST NOT do this, even in the name of improved
   robustness.  In particular, the sequence "<LF>.<LF>" (bare line
   feeds, without carriage returns) MUST NOT be treated as equivalent to
   <CRLF>.<CRLF> as the end of mail data indication.

   Receipt of the end of mail data indication requires the server to
   process the stored mail transaction information.  This processing
   consumes the information in the reverse-path buffer, the forward-path
   buffer, and the mail data buffer, and on the completion of this
   command these buffers are cleared.  If the processing is successful,
   the receiver MUST send an OK reply.  If the processing fails, the
   receiver MUST send a failure reply.  The SMTP model does not allow
   for partial failures at this point: either the message is accepted by
   the server for delivery and a positive response is returned or it is
   not accepted and a failure reply is returned.  In sending a positive
   "250 OK" completion reply to the end of data indication, the receiver
   takes full responsibility for the message (see Section 6.1).  Errors
   that are diagnosed subsequently MUST be reported in a mail message,
   as discussed in Section 4.4.

   When the SMTP server accepts a message either for relaying or for
   final delivery, it inserts a trace record (also referred to
   interchangeably as a "time stamp line" or "Received" line) at the top
   of the mail data.  This trace record indicates the identity of the
   host that sent the message, the identity of the host that received
   the message (and is inserting this time stamp), and the date and time
   the message was received.  Relayed messages will have multiple time
   stamp lines.  Details for formation of these lines, including their
   syntax, is specified in Section 4.4.

   Additional discussion about the operation of the DATA command appears
   in Section 3.3.

   Syntax:

      data = "DATA" CRLF
4.5.2.  Transparency

   Without some provision for data transparency, the character sequence
   "<CRLF>.<CRLF>" ends the mail text and cannot be sent by the user.
   In general, users are not aware of such "forbidden" sequences.  To
   allow all user composed text to be transmitted transparently, the
   following procedures are used:

   o  Before sending a line of mail text, the SMTP client checks the
      first character of the line.  If it is a period, one additional
      period is inserted at the beginning of the line.

   o  When a line of mail text is received by the SMTP server, it checks
      the line.  If the line is composed of a single period, it is
      treated as the end of mail indicator.  If the first character is a
      period and there are other characters on the line, the first
      character is deleted.

   ...

Your DATA command needs to account for that:

  • Your doit() function is expecting a response after sending a string. That is the wrong logic to use while sending the email data. You can't read a response until after the final terminating <CRLF>.<CRLF> has been sent.

  • you have to apply transparency to any line in the email that begins with a . character.

With that said, try something more like this:

int readLine(int sock, string &line)
{
    // read a line from sock until CRLF is reached.
    // I leave this as an exercise for you to implement...

    line = ...;
    return -1 on error, else 0;
}

int readResponse(int sock)
{
    // Please read RFC 5321 section 4.2 for the PROPER format
    // of an SMTP response.  You should be reading from the
    // socket until you receive the terminating
    // "Reply-code [ SP textstring ] CRLF" line...

    string line;

    int r = readLine(sock, line);
    if (r < 0) return r;

    string code = line.substr(0, 3);
    string text = line.substr(4);

    if ((line.length() >= 4) && (line[3] = '-'))
    {
        do
        {
            r = readLine(sock, line);
            if (r < 0) return r;

            text += (" " + line.substr(4));
        }
        while (line.compare(0, 4, code+"-") == 0);
    }

    cout << code << ": " << text << endl;

    return stoi(code);
}

int sendText(int sock, const string &arg)
{
    const char *p = arg.c_str();
    int len = arg.length();

    while (len > 0)
    {
        int r = write(sock, p, len);
        if (r <= 0) return -1;
        p += r;
        len -= r;
    }

    return 0;
}

int sendCmd(int sock, const string &arg)
{
    int r = sendText(sock, arg + "\r\n");
    if (r < 0) return r;
    return readResponse(sock);
}

int main(int argc, char *argv[])
{
    // Make a socket
    int sock = MakeSocket(argv[1], argv[2]);
    cout << "socket is " << sock << endl;
    assert(sock != -1);

    // Begin dialogue

    // read the server greeting first...
    if (readResponse(sock) != 220) {
        // failed, do something...
    }

    if (sendCmd(sock, "HELO " + org.substr(org.find("@") + 1)) != 250) {
        // failed, do something...
    }

    if (sendCmd(sock, "MAIL FROM: <" + org + ">") != 250) {
        // failed, do something...
    }

    int r = sendCmd(sock, "RCPT TO: <" + dest + ">");
    if ((r != 250) && (r != 251)) {
        // failed, do something...
    }

    if (sendCmd(sock, "DATA") != 354) {
        // failed, do something...
    }

    //User writes email here
    while (true) {
        string line;
        getline(cin, line);

        // A line consisting of only "." is a valid line in an email
        // message, so you should not use that as a terminator in your
        // input, use something else, like an EOF marker, or CTRL-C,
        // or something...
        if (some termination condition)
            break;

        // DO NOT call readResponse() here!
        if (!line.empty() && (line[0] == '.')) {
            if (sendText(sock, ".") < 0) {
                // failed, do something...
            }
        }
        if (sendText(sock, line) < 0) {
            // failed, do something...
        }
        if (sendText(sock, "\r\n") < 0) {
            // failed, do something...
        }
    }

    // NOW call readResponse() here!
    if (sendCmd(sock, ".") != 250) {
        // failed, do something...
    }

    sendCmd(sock, "QUIT");
    close(sock);

    return 0;
}

关于c++ - 使用C++发送SMTP邮件,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/42128492/

10-16 21:27