📄 sslsocket.cpp
字号:
/************************************************************************* * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * *************************************************************************/#include "sslsocket.h"/** * Constructor. * * @param host The host this socket is connected to. */SSLSocket::SSLSocket (const string &host) : Socket (host) { this->negotiated = false; this->ssl = NULL; this->ctx = NULL; this->verify = SSL_VERIFY_PEER; this->ca_file = ""; this->ca_dir = ""; SSLeay_add_ssl_algorithms (); ERR_load_crypto_strings (); SSL_load_error_strings ();}/** * Constructor. * * @param host The host this socket is connected to. * @param fd The file descriptor this socket uses for IO. */SSLSocket::SSLSocket (const string &host, int fd) : Socket (host,fd) { this->negotiated = false; this->ssl = NULL; this->ctx = NULL; this->verify = SSL_VERIFY_PEER; this->ca_file = ""; this->ca_dir = ""; SSLeay_add_ssl_algorithms (); ERR_load_crypto_strings (); SSL_load_error_strings ();}/** * Destructor. */SSLSocket::~SSLSocket () { if (this->ssl != NULL) { SSL_shutdown (this->ssl); SSL_free (this->ssl); } if (this->ctx != NULL) { SSL_CTX_free (this->ctx); }}/** * Reads one line from the socket. * * @returns One line read from the socket. * * @throws TransferException * On any uncommon event that occurs while receiving * the data. * @throws SSLException * On any uncommon event that occurs while receiving * data from the encrypted socket. */string SSLSocket::readSocket (){ if (this->negotiated) { return (ssl_readSocket ()); } else { return (Socket::readSocket ()); }}/** * Writes data to the socket. * * @param data The data string that should be written to * the socket. * * @throws TransferException * On any uncommon event that occurs while sending * the data. * @throws SSLException * On any uncommon event that occurs while sending * data through the encrypted socket. */void SSLSocket::writeSocket (const string &data){ if (this->negotiated) { ssl_writeSocket (data); } else { Socket::writeSocket (data); }}/** * Reads one line from the socket. * * @returns One line read from the socket. * * @throws SSLException * On any uncommon event that occurs while receiving * data from the encrypted socket. */string SSLSocket::ssl_readSocket (){ string data; if (this->sockfd != -1) { bool EOL = false; while (!EOL) { char b; int bytes = ssl_read (this->ssl, &b, 1); if (bytes < 1) { throw (SSLException (ssl_error (bytes), "SSLSocket::ssl_readSocket()", 1)); } else if (b != '\n') { data += b; } else { EOL = true; } } } else { throw ( SSLException ( LIBSMTP_I18N_1 ("Socket is closed."), "SSLSocket::ssl_readSocket()", 2 ) ); } LOG_VERBOSE << this->host << ":\t" << data << endl; return (data);}/** * Writes data to the socket. * * @param data The data string that should be written to * the socket. * * @throws SSLException * On any uncommon event that occurs while sending * data through the encrypted socket. */void SSLSocket::ssl_writeSocket (const string &data){ if (this->sockfd != -1) { int ret = ssl_write (this->ssl, data.c_str (), data.length ()); if (ret < 1) { throw ( SSLException ( ssl_error (ret), "SSLSocket::ssl_writeSocket()", 1 ) ); } } else { throw ( SSLException ( LIBSMTP_I18N_1 ("Socket is closed."), "SSLSocket::ssl_writeSocket()", 2 ) ); } LOG_VERBOSE << localhost () << ":\t" << data;}/** * his method wraps around SSL_read and handles the * following error conditions: SSL_ERROR_WANT_WRITE, * SSL_ERROR_WANT_READ, SSL_ERROR_WANT_CONNECT and * SSL_ERROR_WANT_X509_LOOKUP. */int SSLSocket::ssl_read (SSL * ssl, void * buf, int num){ int bytes = SSL_read (ssl, (char*)buf, num); while ( SSL_get_error (this->ssl, bytes) == SSL_ERROR_WANT_WRITE || SSL_get_error (this->ssl, bytes) == SSL_ERROR_WANT_READ || SSL_get_error (this->ssl, bytes) == SSL_ERROR_WANT_CONNECT || SSL_get_error (this->ssl, bytes) == SSL_ERROR_WANT_X509_LOOKUP ) { LOG_ERROR << LIBSMTP_I18N_1 ( "SSL_read() operation did not complete. I try it again.\n" ); bytes = SSL_read (ssl, (char*)buf, num); usleep (100); } return (bytes);}/** * this method wraps around SSL_write and handles the * following error conditions: SSL_ERROR_WANT_WRITE, * SSL_ERROR_WANT_READ, SSL_ERROR_WANT_CONNECT and * SSL_ERROR_WANT_X509_LOOKUP. */int SSLSocket::ssl_write (SSL * ssl, const void * buf, int num){ int ret = SSL_write (ssl, (char*)buf, num); while ( SSL_get_error (this->ssl, ret) == SSL_ERROR_WANT_WRITE || SSL_get_error (this->ssl, ret) == SSL_ERROR_WANT_READ || SSL_get_error (this->ssl, ret) == SSL_ERROR_WANT_CONNECT || SSL_get_error (this->ssl, ret) == SSL_ERROR_WANT_X509_LOOKUP ) { LOG_ERROR << LIBSMTP_I18N_1 ( "SSL_write() operation did not complete. I try it again.\n" ); ret = SSL_write (ssl, (char*)buf, num); usleep (100); } return (ret);}/** * this method wraps around SSL_connect and handles the * following error conditions: SSL_ERROR_WANT_WRITE, * SSL_ERROR_WANT_READ, SSL_ERROR_WANT_CONNECT and * SSL_ERROR_WANT_X509_LOOKUP. */int SSLSocket::ssl_connect (SSL * ssl){ int ret = SSL_connect (ssl); while ( SSL_get_error (this->ssl, ret) == SSL_ERROR_WANT_WRITE || SSL_get_error (this->ssl, ret) == SSL_ERROR_WANT_READ || SSL_get_error (this->ssl, ret) == SSL_ERROR_WANT_CONNECT || SSL_get_error ( this->ssl, ret) == SSL_ERROR_WANT_X509_LOOKUP ) { LOG_ERROR << LIBSMTP_I18N_1 ( "SSL_connect() operation did not complete. I try it again.\n" ); ret = SSL_connect (ssl); usleep (100); } return (ret);}/** * Start the SSL handshake. * * @throws SSLException * If the handshake fails for some reason. * @throws IOException * If one of ca_file or ca_dir is not accessible. */void SSLSocket::negotiate (){ if (this->sockfd != -1) { this->ctx = SSL_CTX_new (SSLv23_client_method ()); if (this->ctx == NULL) { throw (SSLException (ssl_error (), "SSLSocket::negotiate()", 1)); }; this->ssl = SSL_new (this->ctx); if (this->ssl == NULL) { throw (SSLException (ssl_error (), "SSLSocket::negotiate()", 2)); }; int ret = 1; if (this->ca_file.length () || this->ca_dir.length ()) { // check accessability of this->ca_file if (this->ca_file.length ()) { if (access (this->ca_file.c_str (), F_OK) != 0) { string msg = LIBSMTP_I18N_2 ("$0 does not exist.", this->ca_file); throw (IOException (msg, "SSLSocket::negotiate()", 7)); } else if (access (this->ca_file.c_str (), R_OK) != 0) { string msg = LIBSMTP_I18N_2 ("$0 is not readable.", this->ca_file); throw (IOException (msg, "SSLSocket::negotiate()", 8)); } } // check accessability of this->ca_dir if (this->ca_dir.length ()) { if (this->ca_dir != "" && access (this->ca_dir.c_str (), F_OK) != 0) { string msg = LIBSMTP_I18N_2 ("$0 does not exist.", this->ca_dir); throw (IOException (msg, "SSLSocket::negotiate()", 9)); } else if (this->ca_file != "" && access (this->ca_file.c_str (), R_OK) != 0) { string msg = LIBSMTP_I18N_2 ("$0 is not readable.", this->ca_file); throw (IOException (msg, "SSLSocket::negotiate()", 10)); } } ret = SSL_CTX_load_verify_locations ( this->ctx, this->ca_file == "" ? NULL : this->ca_file.c_str (), this->ca_dir == "" ? NULL : this->ca_dir.c_str () ); if (ret != 1) { throw (SSLException (ssl_error (), "SSLSocket::negotiate()", 3)); }; } SSL_set_verify (this->ssl, this->verify , 0); SSL_set_fd (this->ssl, this->sockfd); ret = ssl_connect (this->ssl); if (ret < 1) { throw (SSLException (ssl_error (ret), "SSLSocket::negotiate()", 4)); }; if (this->verify != SSL_VERIFY_NONE) { if ( SSL_get_verify_result (this->ssl) != X509_V_OK || SSL_get_peer_certificate (this->ssl) == NULL ) { throw (SSLException (ssl_error (), "SSLSocket::negotiate()", 5)); } } } else { throw (SSLException (LIBSMTP_I18N_1 ("Socket is closed."), "SSLSocket::negotiate()", 6)); } dumpSSLInfo (); this->negotiated = true;}/** * Set some SSL specific options. * * @param opts The option that should be set. */void SSLSocket::setSSLOpts (SSL_OPTS opts){ if (opts == VERIFY_NONE) { this->verify = SSL_VERIFY_NONE; } else if (opts == VERIFY_PEER) { this->verify = SSL_VERIFY_PEER; }}/** * Returns the last error on the SSL error stack as a * string. * * @param ret The return code of the last openssl function that * was called. * @returns The error description. */string SSLSocket::ssl_error (int ret){ string str_error = LIBSMTP_I18N_1 ("An unknown error occured."); if (SSL_get_error (this->ssl, ret) == SSL_ERROR_SYSCALL && ERR_get_error () == 0) { if (ret == 0) { str_error = LIBSMTP_I18N_1 ("Socket was closed."); } else if (ret == -1) { str_error = strerror (errno); } } else { char error[1024]; ERR_error_string_n (ERR_get_error (), error, 1024); str_error = error; } return (str_error);}/** * Set the locations of the certificate files. * ca_file points to a file containing PEM * certs, ca_dir points to a directory containing * PEM certs. "man 3 SSL_CTX_load_verify_locations" * will give you more info on that topic. * * @param ca_file A file that contains PEM certificates. * The file can contain several CA certificates * identified by * * -----BEGIN CERTIFICATE----- * * ... [CA certificate in base64 encoding] ... * * -----END CERTIFICATE----- * * sequences. Before, between, and after the * certificates text is allowed which * can be used e.g. for descriptions of the * certificates. * * Take a look in the openssl documentation to * get more infos on that topic. * @param ca_dir A directory that contains PEM certificates. * The files each contain one CA certificate. The files * are looked up by the CA subject name hash value, which * must hence be available. If more than one CA certificate * with the same name hash value exist, the extension must be * different (e.g. 9d66eef0.0, 9d66eef0.1 etc). The search * is performed in the ordering of the extension number, * regardless of other properties of the certificates. * Use the c_rehash utility to create the necessary links. * * Take a look in the openssl documentation to * get more infos on that topic. */void SSLSocket::setVerifyLocations (const string &ca_file, const string &ca_dir){ this->ca_file = ca_file; this->ca_dir = ca_dir;}/** * Dump various X509 Infos. */void SSLSocket::dumpSSLInfo (){ char * str = NULL; str = (char*)SSL_get_cipher (this->ssl); if (str == NULL) { return; } LOG_VERBOSE << LIBSMTP_I18N_2 ("SSLInfo:\tSSL connection using $0 ", str) << endl; X509 * server_cert = SSL_get_peer_certificate (this->ssl); if (server_cert == NULL) LOG_VERBOSE << LIBSMTP_I18N_1 ("SSLInfo:\tserver certificate - subject:") << endl; LOG_VERBOSE << "SSLInfo:\t=============================" << endl; // X509_NAME_oneline should not be used according to the manual page, // but i dunno any other way to get the X509 infos str = X509_NAME_oneline (X509_get_subject_name (server_cert), 0, 0); if (str == NULL) { return; } vector<string> tokens = String::string2tokens (str, '/'); for (unsigned int idx = 0; idx < tokens.size (); idx++) { vector<string> value = String::string2tokens (tokens[idx], '='); if (value.size () > 1) { LOG_VERBOSE << "SSLInfo:\t" << value[0] << "\t=\t" << value[1] << endl; } } free (str); LOG_VERBOSE << LIBSMTP_I18N_1 ("SSLInfo:\tServer certificate - issuer:") << endl; LOG_VERBOSE << "SSLInfo:\t=============================" << endl; // X509_NAME_oneline should not be used according to the manual page, // but i dunno any other way to get the X509 infos str = X509_NAME_oneline (X509_get_issuer_name (server_cert), 0, 0); if (str == NULL) { return; } tokens = String::string2tokens (str, '/'); for (unsigned int idx = 0; idx < tokens.size (); idx++) { vector<string> value = String::string2tokens (tokens[idx], '='); if (value.size () > 1) { LOG_VERBOSE << "SSLInfo:\t" << value[0] << "\t=\t" << value[1] << endl; } } free (str);}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -