diff options
author | Adam Langley <agl@google.com> | 2016-07-08 00:52:02 +0300 |
---|---|---|
committer | David Benjamin <davidben@google.com> | 2016-07-11 21:45:01 +0300 |
commit | 403c52aa94760051f93e4388a2966c0fd9422c2b (patch) | |
tree | 643947e1e6964f627215ce79629da0fdc7276e67 /tool/transport_common.cc | |
parent | abe22b605fe749cde743632d01cc09125684dd3e (diff) |
Support “-starttls smtp” in `bssl client`
This change adds support for doing an SMTP STARTTLS dance before a TLS
handshake when using the tool. This is useful for poking at SMTP
servers.
Change-Id: I04cd60d02d3377cce83e412d62e3257235a19116
Reviewed-on: https://boringssl-review.googlesource.com/8662
Reviewed-by: David Benjamin <davidben@google.com>
Diffstat (limited to 'tool/transport_common.cc')
-rw-r--r-- | tool/transport_common.cc | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/tool/transport_common.cc b/tool/transport_common.cc index 37bf0e07..735a349a 100644 --- a/tool/transport_common.cc +++ b/tool/transport_common.cc @@ -18,6 +18,7 @@ #include <vector> #include <errno.h> +#include <limits.h> #include <stddef.h> #include <stdlib.h> #include <string.h> @@ -344,3 +345,195 @@ bool TransferData(SSL *ssl, int sock) { } } } + +// SocketLineReader wraps a small buffer around a socket for line-orientated +// protocols. +class SocketLineReader { + public: + explicit SocketLineReader(int sock) : sock_(sock) {} + + // Next reads a '\n'- or '\r\n'-terminated line from the socket and, on + // success, sets |*out_line| to it and returns true. Otherwise it returns + // false. + bool Next(std::string *out_line) { + for (;;) { + for (size_t i = 0; i < buf_len_; i++) { + if (buf_[i] != '\n') { + continue; + } + + size_t length = i; + if (i > 0 && buf_[i - 1] == '\r') { + length--; + } + + out_line->assign(buf_, length); + buf_len_ -= i + 1; + memmove(buf_, &buf_[i + 1], buf_len_); + + return true; + } + + if (buf_len_ == sizeof(buf_)) { + fprintf(stderr, "Received line too long!\n"); + return false; + } + + ssize_t n; + do { + n = recv(sock_, &buf_[buf_len_], sizeof(buf_) - buf_len_, 0); + } while (n == -1 && errno == EINTR); + + if (n < 0) { + fprintf(stderr, "Read error from socket\n"); + return false; + } + + buf_len_ += n; + } + } + + // ReadSMTPReply reads one or more lines that make up an SMTP reply. On + // success, it sets |*out_code| to the reply's code (e.g. 250) and + // |*out_content| to the body of the reply (e.g. "OK") and returns true. + // Otherwise it returns false. + // + // See https://tools.ietf.org/html/rfc821#page-48 + bool ReadSMTPReply(unsigned *out_code, std::string *out_content) { + out_content->clear(); + + // kMaxLines is the maximum number of lines that we'll accept in an SMTP + // reply. + static const unsigned kMaxLines = 512; + for (unsigned i = 0; i < kMaxLines; i++) { + std::string line; + if (!Next(&line)) { + return false; + } + + if (line.size() < 4) { + fprintf(stderr, "Short line from SMTP server: %s\n", line.c_str()); + return false; + } + + const std::string code_str = line.substr(0, 3); + char *endptr; + const unsigned long code = strtoul(code_str.c_str(), &endptr, 10); + if (*endptr || code > UINT_MAX) { + fprintf(stderr, "Failed to parse code from line: %s\n", line.c_str()); + return false; + } + + if (i == 0) { + *out_code = code; + } else if (code != *out_code) { + fprintf(stderr, + "Reply code varied within a single reply: was %u, now %u\n", + *out_code, static_cast<unsigned>(code)); + return false; + } + + if (line[3] == ' ') { + // End of reply. + *out_content += line.substr(4, std::string::npos); + return true; + } else if (line[3] == '-') { + // Another line of reply will follow this one. + *out_content += line.substr(4, std::string::npos); + out_content->push_back('\n'); + } else { + fprintf(stderr, "Bad character after code in SMTP reply: %s\n", + line.c_str()); + return false; + } + } + + fprintf(stderr, "Rejected SMTP reply of more then %u lines\n", kMaxLines); + return false; + } + + private: + const int sock_; + char buf_[512]; + size_t buf_len_ = 0; +}; + +// SendAll writes |data_len| bytes from |data| to |sock|. It returns true on +// success and false otherwise. +static bool SendAll(int sock, const char *data, size_t data_len) { + size_t done = 0; + + while (done < data_len) { + ssize_t n; + do { + n = send(sock, &data[done], data_len - done, 0); + } while (n == -1 && errno == EINTR); + + if (n < 0) { + fprintf(stderr, "Error while writing to socket\n"); + return false; + } + + done += n; + } + + return true; +} + +bool DoSMTPStartTLS(int sock) { + SocketLineReader line_reader(sock); + + unsigned code_220; + std::string reply_220; + if (!line_reader.ReadSMTPReply(&code_220, &reply_220)) { + return false; + } + + if (code_220 != 220) { + fprintf(stderr, "Expected 220 line from SMTP server but got code %u\n", + code_220); + return false; + } + + static const char kHelloLine[] = "EHLO BoringSSL\r\n"; + if (!SendAll(sock, kHelloLine, sizeof(kHelloLine) - 1)) { + return false; + } + + unsigned code_250; + std::string reply_250; + if (!line_reader.ReadSMTPReply(&code_250, &reply_250)) { + return false; + } + + if (code_250 != 250) { + fprintf(stderr, "Expected 250 line after EHLO but got code %u\n", code_250); + return false; + } + + // https://tools.ietf.org/html/rfc1869#section-4.3 + if (reply_250.find("\nSTARTTLS\n") == std::string::npos && + reply_250.find("\nSTARTTLS") != reply_250.size() - 8) { + fprintf(stderr, "Server does not support STARTTLS\n"); + return false; + } + + static const char kSTARTTLSLine[] = "STARTTLS\r\n"; + if (!SendAll(sock, kSTARTTLSLine, sizeof(kSTARTTLSLine) - 1)) { + return false; + } + + if (!line_reader.ReadSMTPReply(&code_220, &reply_220)) { + return false; + } + + if (code_220 != 220) { + fprintf( + stderr, + "Expected 220 line from SMTP server after STARTTLS, but got code %u\n", + code_220); + return false; + } + + return true; +} |