📄 ssl.c
字号:
/* * Part of Very Secure FTPd * Licence: GPL v2. Note that this code interfaces with with the OpenSSL * libraries, so please read LICENSE where I give explicit permission to link * against the OpenSSL libraries. * Author: Chris Evans * ssl.c * * Routines to handle a SSL/TLS-based implementation of RFC 2228, i.e. * encryption. */#include "ssl.h"#include "session.h"#include "ftpcodes.h"#include "ftpcmdio.h"#include "defs.h"#include "str.h"#include "sysutil.h"#include "tunables.h"#include "utility.h"#include "builddefs.h"#include "logging.h"#ifdef VSF_BUILD_SSL#include <openssl/ssl.h>#include <openssl/err.h>#include <openssl/rand.h>#include <openssl/bio.h>#include <errno.h>static char* get_ssl_error();static SSL* get_ssl(struct vsf_session* p_sess, int fd);static int ssl_session_init(struct vsf_session* p_sess);static void setup_bio_callbacks();static long bio_callback( BIO* p_bio, int oper, const char* p_arg, int argi, long argl, long retval);static int ssl_verify_callback(int verify_ok, X509_STORE_CTX* p_ctx);static int ssl_cert_digest( SSL* p_ssl, struct vsf_session* p_sess, struct mystr* p_str);static void maybe_log_shutdown_state(struct vsf_session* p_sess);static void maybe_log_ssl_error_state(struct vsf_session* p_sess, int ret);static int ssl_read_common(struct vsf_session* p_sess, SSL* p_ssl, char* p_buf, unsigned int len, int (*p_ssl_func)(SSL*, void*, int));static int ssl_inited;static struct mystr debug_str;voidssl_init(struct vsf_session* p_sess){ if (!ssl_inited) { SSL_CTX* p_ctx; long options; int verify_option = 0; SSL_library_init(); p_ctx = SSL_CTX_new(SSLv23_server_method()); if (p_ctx == NULL) { die("SSL: could not allocate SSL context"); } options = SSL_OP_ALL; if (!tunable_sslv2) { options |= SSL_OP_NO_SSLv2; } if (!tunable_sslv3) { options |= SSL_OP_NO_SSLv3; } if (!tunable_tlsv1) { options |= SSL_OP_NO_TLSv1; } SSL_CTX_set_options(p_ctx, options); if (tunable_rsa_cert_file) { const char* p_key = tunable_rsa_private_key_file; if (!p_key) { p_key = tunable_rsa_cert_file; } if (SSL_CTX_use_certificate_chain_file(p_ctx, tunable_rsa_cert_file) != 1) { die("SSL: cannot load RSA certificate"); } if (SSL_CTX_use_PrivateKey_file(p_ctx, p_key, X509_FILETYPE_PEM) != 1) { die("SSL: cannot load RSA private key"); } } if (tunable_dsa_cert_file) { const char* p_key = tunable_dsa_private_key_file; if (!p_key) { p_key = tunable_dsa_cert_file; } if (SSL_CTX_use_certificate_chain_file(p_ctx, tunable_dsa_cert_file) != 1) { die("SSL: cannot load DSA certificate"); } if (SSL_CTX_use_PrivateKey_file(p_ctx, p_key, X509_FILETYPE_PEM) != 1) { die("SSL: cannot load DSA private key"); } } if (tunable_ssl_ciphers && SSL_CTX_set_cipher_list(p_ctx, tunable_ssl_ciphers) != 1) { die("SSL: could not set cipher list"); } if (RAND_status() != 1) { die("SSL: RNG is not seeded"); } if (tunable_ssl_request_cert) { verify_option |= SSL_VERIFY_PEER; } if (tunable_require_cert) { verify_option |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; } if (verify_option) { SSL_CTX_set_verify(p_ctx, verify_option, ssl_verify_callback); if (tunable_ca_certs_file) { if (!SSL_CTX_load_verify_locations(p_ctx, tunable_ca_certs_file, NULL)) { die("SSL: could not load verify file"); } } } { char* p_ctx_id = "vsftpd"; SSL_CTX_set_session_id_context(p_ctx, (void*) p_ctx_id, vsf_sysutil_strlen(p_ctx_id)); } p_sess->p_ssl_ctx = p_ctx; ssl_inited = 1; }}voidssl_control_handshake(struct vsf_session* p_sess){ if (!ssl_session_init(p_sess)) { struct mystr err_str = INIT_MYSTR; str_alloc_text(&err_str, "Negotiation failed: "); /* Technically, we shouldn't leak such detailed error messages. */ str_append_text(&err_str, get_ssl_error()); vsf_cmdio_write_str(p_sess, FTP_TLS_FAIL, &err_str); vsf_sysutil_exit(0); } p_sess->control_use_ssl = 1;}voidhandle_auth(struct vsf_session* p_sess){ str_upper(&p_sess->ftp_arg_str); if (str_equal_text(&p_sess->ftp_arg_str, "TLS") || str_equal_text(&p_sess->ftp_arg_str, "TLS-C") || str_equal_text(&p_sess->ftp_arg_str, "SSL") || str_equal_text(&p_sess->ftp_arg_str, "TLS-P")) { vsf_cmdio_write(p_sess, FTP_AUTHOK, "Proceed with negotiation."); ssl_control_handshake(p_sess); if (str_equal_text(&p_sess->ftp_arg_str, "SSL") || str_equal_text(&p_sess->ftp_arg_str, "TLS-P")) { p_sess->data_use_ssl = 1; } } else { vsf_cmdio_write(p_sess, FTP_BADAUTH, "Unknown AUTH type."); }}voidhandle_pbsz(struct vsf_session* p_sess){ if (!p_sess->control_use_ssl) { vsf_cmdio_write(p_sess, FTP_BADPBSZ, "PBSZ needs a secure connection."); } else { vsf_cmdio_write(p_sess, FTP_PBSZOK, "PBSZ set to 0."); }}voidhandle_prot(struct vsf_session* p_sess){ str_upper(&p_sess->ftp_arg_str); if (!p_sess->control_use_ssl) { vsf_cmdio_write(p_sess, FTP_BADPROT, "PROT needs a secure connection."); } else if (str_equal_text(&p_sess->ftp_arg_str, "C")) { p_sess->data_use_ssl = 0; vsf_cmdio_write(p_sess, FTP_PROTOK, "PROT now Clear."); } else if (str_equal_text(&p_sess->ftp_arg_str, "P")) { p_sess->data_use_ssl = 1; vsf_cmdio_write(p_sess, FTP_PROTOK, "PROT now Private."); } else if (str_equal_text(&p_sess->ftp_arg_str, "S") || str_equal_text(&p_sess->ftp_arg_str, "E")) { vsf_cmdio_write(p_sess, FTP_NOHANDLEPROT, "PROT not supported."); } else { vsf_cmdio_write(p_sess, FTP_NOSUCHPROT, "PROT not recognized."); }}intssl_read(struct vsf_session* p_sess, void* p_ssl, char* p_buf, unsigned int len){ return ssl_read_common(p_sess, (SSL*) p_ssl, p_buf, len, SSL_read);}intssl_peek(struct vsf_session* p_sess, void* p_ssl, char* p_buf, unsigned int len){ return ssl_read_common(p_sess, (SSL*) p_ssl, p_buf, len, SSL_peek);}static intssl_read_common(struct vsf_session* p_sess, SSL* p_void_ssl, char* p_buf, unsigned int len, int (*p_ssl_func)(SSL*, void*, int)){ int retval; int err; SSL* p_ssl = (SSL*) p_void_ssl; do { retval = (*p_ssl_func)(p_ssl, p_buf, len); err = SSL_get_error(p_ssl, retval); } while (retval < 0 && (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE)); // If we hit an EOF, make sure it was from the peer, not injected by the // attacker. if (retval == 0 && SSL_get_shutdown(p_ssl) != SSL_RECEIVED_SHUTDOWN) { str_alloc_text(&debug_str, "Connection terminated without SSL shutdown " "- buggy client?"); vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str); if (tunable_strict_ssl_read_eof) { return -1; } } return retval;}intssl_write(void* p_ssl, const char* p_buf, unsigned int len){ int retval; int err; do { retval = SSL_write((SSL*) p_ssl, p_buf, len); err = SSL_get_error((SSL*) p_ssl, retval); } while (retval < 0 && (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE)); return retval;}intssl_write_str(void* p_ssl, const struct mystr* p_str){ unsigned int len = str_getlen(p_str); int ret = SSL_write((SSL*) p_ssl, str_getbuf(p_str), len); if ((unsigned int) ret != len) { return -1; } return 0;}intssl_read_into_str(struct vsf_session* p_sess, void* p_ssl, struct mystr* p_str){ unsigned int len = str_getlen(p_str); int ret = ssl_read(p_sess, p_ssl, (char*) str_getbuf(p_str), len); if (ret >= 0) { str_trunc(p_str, (unsigned int) ret); } else { str_empty(p_str); } return ret;}static voidmaybe_log_shutdown_state(struct vsf_session* p_sess){ if (tunable_debug_ssl) { int ret = SSL_get_shutdown(p_sess->p_data_ssl); str_alloc_text(&debug_str, "SSL shutdown state is: "); if (ret == 0) { str_append_text(&debug_str, "NONE"); } else if (ret == SSL_SENT_SHUTDOWN) { str_append_text(&debug_str, "SSL_SENT_SHUTDOWN"); } else if (ret == SSL_RECEIVED_SHUTDOWN) { str_append_text(&debug_str, "SSL_RECEIVED_SHUTDOWN"); } else { str_append_ulong(&debug_str, ret); } vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str); }}static voidmaybe_log_ssl_error_state(struct vsf_session* p_sess, int ret){ if (tunable_debug_ssl) { str_alloc_text(&debug_str, "SSL ret: "); str_append_ulong(&debug_str, ret); str_append_text(&debug_str, ", SSL error: "); str_append_text(&debug_str, get_ssl_error()); str_append_text(&debug_str, ", errno: "); str_append_ulong(&debug_str, errno); vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str); }}intssl_data_close(struct vsf_session* p_sess){ int success = 1; SSL* p_ssl = p_sess->p_data_ssl; if (p_ssl) { int ret; maybe_log_shutdown_state(p_sess); // This is a mess. Ideally, when we're the sender, we'd like to get to the // SSL_RECEIVED_SHUTDOWN state to get a cryptographic guarantee that the // peer received all the data and shut the connection down cleanly. It // doesn't matter hugely apart from logging, but it's a nagging detail. // Unfortunately, no FTP client I found was able to get sends into that // state, so the best we can do is issue SSL_shutdown but not check the // errors / returns. At least this enables the receiver to be sure of the // integrity of the send in terms of unwanted truncation. ret = SSL_shutdown(p_ssl); maybe_log_shutdown_state(p_sess); if (ret == 0) { ret = SSL_shutdown(p_ssl); maybe_log_shutdown_state(p_sess); if (ret != 1) { if (tunable_strict_ssl_write_shutdown) { success = 0; } maybe_log_shutdown_state(p_sess); maybe_log_ssl_error_state(p_sess, ret); } } else if (ret < 0) { if (tunable_strict_ssl_write_shutdown) { success = 0; } maybe_log_ssl_error_state(p_sess, ret); }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -