/* * Copyright (c) 1999 - 2002 Peter 'Luna' Runestig * All rights reserved. * * Redistribution and use in source and binary forms, with or without modifi- * cation, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright no- * tice, this list of conditions and the following disclaimer in the do- * cumentation and/or other materials provided with the distribution. * * o The names of the contributors may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LI- * ABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUEN- * TIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEV- * ER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABI- * LITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifdef HAVE_CONFIG_H #include #endif #ifndef lint static char copyright[] = "@(#) Copyright (c) Peter 'Luna' Runestig 1999 - 2002 .\n"; #endif /* not lint */ #if !defined(PR_OBSD_FTP) && !defined(PR_TELNET) #error You must define PR_OBSD_FTP or PR_TELNET ! #endif #include #include #include #include #include #ifdef HAVE_NETDB_H #include #endif /* HAVE_NETDB_H */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef TLS_SESSION_FILE_CACHE #include #include #include #include #endif /* TLS_SESSION_FILE_CACHE */ #ifdef PR_TELNET #include "ring.h" #include "externs.h" #define MODE_EDIT 0x01 void NetNonblockingIO(int fd, int onoff); extern int net; /* the socket */ extern int tin; /* stdin */ extern char *hostname; FILE *ttyout; #define SOCK_TO_SSL(s) \ ( tls_active ? ssl : NULL ) #endif /* PR_TELNET */ #ifdef PR_OBSD_FTP # ifdef __STDC__ # include # else # include # endif typedef struct _CONN { SSL *ssl; int sock; } CONN; #define SOCK_TO_SSL(s) \ ( s == data_conn.sock ? data_conn.ssl : \ ( s == ctrl_conn.sock ? ctrl_conn.ssl : NULL ) ) extern FILE *ttyout; /* stdout or stderr, depending on interactive */ #endif /* PR_OBSD_FTP */ /* define if you want to check for OpenSSL-related memory leaks */ /* #define DEBUG_OPENSSL_MEM */ #define FPUTC_BUFFERSIZE 1024 #define DEFAULTCIPHERLIST "HIGH:MEDIUM:LOW:+KRB5:+ADH:+EXP" void tls_cleanup(void); void tls_fputc_fflush(int fd); int x509rc_read_filenames(void); static unsigned char fputc_buffer[FPUTC_BUFFERSIZE]; static int fputc_buflen = 0; static int verify_error_flag = 0; static int x509rc_override = 0; static int tls_nozlib = 0; char *tls_key_file = NULL; char *tls_cert_file = NULL; char *tls_capath_dir = NULL; char *tls_cafile_file = NULL; char *tls_crl_file = NULL; char *tls_crl_dir = NULL; char *tls_rand_file = NULL; char *tls_hostname = NULL; /* hostname used by user to connect */ char *tls_cipher_list = NULL; static SSL_CTX *ssl_ctx = NULL; static X509_STORE *crl_store = NULL; static SSL_METHOD *tls_method = NULL; #ifdef PR_TELNET int tls_active = 0; int tls_suspend_iacs = 0; int tls_reset_state = 0; static SSL *ssl = NULL; #endif /* PR_TELNET */ #ifdef PR_OBSD_FTP CONN data_conn = { NULL, -1 }, ctrl_conn = { NULL, -1 }; SSL *ctrl_ssl_clone = NULL; int tls_want_on_ctrl = 1; int tls_want_on_data = 0; int tls_no_verify = 0; #endif /* PR_OBSD_FTP */ void tls_set_cipher_list(char *list) { if (tls_cipher_list) free(tls_cipher_list); tls_cipher_list = strdup(list); } #ifdef PR_OBSD_FTP int tls_active(int s) { if (SOCK_TO_SSL(s)) return 1; else return 0; } #endif /* PR_OBSD_FTP */ char *tls_get_cipher_info_string(int fd) { static char r[60]; SSL *s = SOCK_TO_SSL(fd); if (s) snprintf(r, sizeof(r), "cipher %s (%d bits)", SSL_get_cipher(s), SSL_get_cipher_bits(s, NULL)); else snprintf(r, sizeof(r), "clear"); return r; } void tls_set_defaults(void) { #ifdef ZLIB /* compression doesn't work with SSLv23_client_method() */ tls_method = SSLv3_client_method(); #else tls_method = SSLv23_client_method(); #endif /* !ZLIB */ if (!tls_cipher_list) tls_cipher_list = strdup(DEFAULTCIPHERLIST); } int tls_optarg(char *optarg) { char *p; if ((p = strchr(optarg, '='))) { *p++ = 0; if (!strcmp(optarg, "cert")) { tls_cert_file = strdup(p); x509rc_override = 1; } else if (!strcmp(optarg, "key")) tls_key_file = strdup(p); else if (!strcmp(optarg, "CAfile")) tls_cafile_file = strdup(p); else if (!strcmp(optarg, "CApath")) tls_capath_dir = strdup(p); else if (!strcmp(optarg, "crlfile")) tls_crl_file = strdup(p); else if (!strcmp(optarg, "crldir")) tls_crl_dir = strdup(p); else if (!strcmp(optarg, "cipher")) tls_set_cipher_list(p); else return 1; } else if (!strcmp(optarg, "sslv3")) tls_method = SSLv3_client_method(); else if (!strcmp(optarg, "tlsv1")) tls_method = TLSv1_client_method(); else if (!strcmp(optarg, "nozlib")) tls_nozlib = 1; #ifdef PR_OBSD_FTP else if (!strcmp(optarg, "noprotect")) tls_want_on_ctrl = 0; else if (!strcmp(optarg, "private")) tls_want_on_data = 1; else if (!strcmp(optarg, "certsok")) tls_no_verify = 1; #endif /* PR_OBSD_FTP */ else return 1; return 0; } char **tls_get_SAN_objs(SSL *s, int type) /* returns NULL or an array of malloc'ed objects of type `type' from the server's * subjectAltName, remember to free() them all! */ { #define NUM_SAN_OBJS 50 static char *objs[NUM_SAN_OBJS]; char **rv = NULL; X509 *server_cert = NULL; int i, j; X509_EXTENSION *ext = NULL; STACK_OF(GENERAL_NAME) *ialt = NULL; GENERAL_NAME *gen = NULL; memset(objs, 0, sizeof(objs)); if ((server_cert = SSL_get_peer_certificate(s))) { if ((i = X509_get_ext_by_NID(server_cert, NID_subject_alt_name, -1)) < 0) goto eject; if (!(ext = X509_get_ext(server_cert, i))) goto eject; if (!(ialt = X509V3_EXT_d2i(ext))) goto eject; rv = objs; for (i = 0, j = 0; i < sk_GENERAL_NAME_num(ialt) && j < NUM_SAN_OBJS - 2; i++) { gen = sk_GENERAL_NAME_value(ialt, i); if (gen->type == type) { if(!gen->d.ia5 || !gen->d.ia5->length) continue; objs[j] = malloc(gen->d.ia5->length + 1); if (objs[j]) { memcpy(objs[j], gen->d.ia5->data, gen->d.ia5->length); objs[j][gen->d.ia5->length] = 0; j++; } } GENERAL_NAME_free(gen); } } eject: if (ialt) sk_GENERAL_NAME_free(ialt); if (server_cert) X509_free(server_cert); return rv; } char *x509v3_subjectAltName_oneline(SSL *s, char *buf, int len) { X509 *server_cert = NULL; X509_EXTENSION *ext = NULL; BIO *mem = NULL; char *data = NULL, *rv = NULL; if ((server_cert = SSL_get_peer_certificate(s))) { int i, data_len = 0, ok; if ((i = X509_get_ext_by_NID(server_cert, NID_subject_alt_name, -1)) < 0) goto eject; if (!(ext = X509_get_ext(server_cert, i))) goto eject; if (!(mem = BIO_new(BIO_s_mem()))) goto eject; ok = X509V3_EXT_print(mem, ext, 0, 0); if (ok) data_len = BIO_get_mem_data(mem, &data); if (data) { /* the 'data' returned is not '\0' terminated */ if (buf) { memcpy(buf, data, data_len < len ? data_len : len); buf[data_len < len ? data_len : len - 1] = 0; rv = buf; goto eject; } else { char *b = malloc(data_len + 1); if (b) { memcpy(b, data, data_len); b[data_len] = 0; } rv = b; goto eject; } } else goto eject; } eject: if (server_cert) X509_free(server_cert); if (mem) BIO_free(mem); return rv; } char *tls_get_commonName(SSL *s) { static char name[256]; int err = 0; X509 *server_cert; if ((server_cert = SSL_get_peer_certificate(s))) { err = X509_NAME_get_text_by_NID(X509_get_subject_name(server_cert), NID_commonName, name, sizeof(name)); X509_free(server_cert); } if (err > 0) return name; else return NULL; } /* if we are using OpenSSL 0.9.6 or newer, we want to use X509_NAME_print_ex() * instead of X509_NAME_oneline(). */ char *x509_name_oneline(X509_NAME *n, char *buf, int len) { #if OPENSSL_VERSION_NUMBER < 0x000906000 return X509_NAME_oneline(n, buf, len); #else BIO *mem = BIO_new(BIO_s_mem()); char *data = NULL; int data_len = 0, ok; ok = X509_NAME_print_ex(mem, n, 0, XN_FLAG_ONELINE); if (ok) data_len = BIO_get_mem_data(mem, &data); if (data) { /* the 'data' returned is not '\0' terminated */ if (buf) { memcpy(buf, data, data_len < len ? data_len : len); buf[data_len < len ? data_len : len - 1] = 0; BIO_free(mem); return buf; } else { char *b = malloc(data_len + 1); if (b) { memcpy(b, data, data_len); b[data_len] = 0; } BIO_free(mem); return b; } } else { BIO_free(mem); return NULL; } #endif /* OPENSSL_VERSION_NUMBER >= 0x000906000 */ } char *tls_get_issuer_name(SSL *s) { static char name[256]; X509 *server_cert; if ((server_cert = SSL_get_peer_certificate(s))) { char *n = x509_name_oneline(X509_get_issuer_name(server_cert), name, sizeof(name)); X509_free(server_cert); return n; } else /* no certificate from server */ return NULL; } char *tls_get_subject_name(SSL *s) { static char name[256]; X509 *server_cert; if ((server_cert = SSL_get_peer_certificate(s))) { char *n = x509_name_oneline(X509_get_subject_name(server_cert), name, sizeof(name)); X509_free(server_cert); return n; } else /* no certificate from server */ return NULL; } static char read_char(void) { char inl[10]; #ifdef PR_TELNET /* we must add MODE_EDIT for the fgets(), or CR won't terminate the line */ int mode = getconnmode(); TerminalNewMode(mode | MODE_EDIT); NetNonblockingIO(tin, 0); fgets(inl, sizeof(inl), stdin); NetNonblockingIO(tin, 1); TerminalNewMode(mode); #else fgets(inl, sizeof(inl), stdin); #endif /* !PR_TELNET */ return *inl; } /* this one is (very much!) based on work by Ralf S. Engelschall . * comments by Ralf. */ static int verify_crl(int ok, X509_STORE_CTX *ctx) { X509_OBJECT obj; X509_NAME *subject; X509_NAME *issuer; X509 *xs; X509_CRL *crl; X509_REVOKED *revoked; X509_STORE_CTX store_ctx; long serial; int i, n, rc; char *cp; /* * Unless a revocation store for CRLs was created we * cannot do any CRL-based verification, of course. */ if (!crl_store) return ok; /* * Determine certificate ingredients in advance */ xs = X509_STORE_CTX_get_current_cert(ctx); subject = X509_get_subject_name(xs); issuer = X509_get_issuer_name(xs); /* * OpenSSL provides the general mechanism to deal with CRLs but does not * use them automatically when verifying certificates, so we do it * explicitly here. We will check the CRL for the currently checked * certificate, if there is such a CRL in the store. * * We come through this procedure for each certificate in the certificate * chain, starting with the root-CA's certificate. At each step we've to * both verify the signature on the CRL (to make sure it's a valid CRL) * and it's revocation list (to make sure the current certificate isn't * revoked). But because to check the signature on the CRL we need the * public key of the issuing CA certificate (which was already processed * one round before), we've a little problem. But we can both solve it and * at the same time optimize the processing by using the following * verification scheme (idea and code snippets borrowed from the GLOBUS * project): * * 1. We'll check the signature of a CRL in each step when we find a CRL * through the _subject_ name of the current certificate. This CRL * itself will be needed the first time in the next round, of course. * But we do the signature processing one round before this where the * public key of the CA is available. * * 2. We'll check the revocation list of a CRL in each step when * we find a CRL through the _issuer_ name of the current certificate. * This CRLs signature was then already verified one round before. * * This verification scheme allows a CA to revoke its own certificate as * well, of course. */ /* * Try to retrieve a CRL corresponding to the _subject_ of * the current certificate in order to verify it's integrity. */ memset((char *)&obj, 0, sizeof(obj)); X509_STORE_CTX_init(&store_ctx, crl_store, NULL, NULL); rc = X509_STORE_get_by_subject(&store_ctx, X509_LU_CRL, subject, &obj); X509_STORE_CTX_cleanup(&store_ctx); crl = obj.data.crl; if (rc > 0 && crl != NULL) { /* * Verify the signature on this CRL */ if (X509_CRL_verify(crl, X509_get_pubkey(xs)) <= 0) { fprintf(stderr, "Invalid signature on CRL!\r\n"); X509_STORE_CTX_set_error(ctx, X509_V_ERR_CRL_SIGNATURE_FAILURE); X509_OBJECT_free_contents(&obj); return 0; } /* * Check date of CRL to make sure it's not expired */ i = X509_cmp_current_time(X509_CRL_get_nextUpdate(crl)); if (i == 0) { fprintf(stderr, "Found CRL has invalid nextUpdate field.\r\n"); X509_STORE_CTX_set_error(ctx, X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD); X509_OBJECT_free_contents(&obj); return 0; } if (i < 0) { fprintf(stderr, "Found CRL is expired - revoking all certificates until you get updated CRL.\r\n"); X509_STORE_CTX_set_error(ctx, X509_V_ERR_CRL_HAS_EXPIRED); X509_OBJECT_free_contents(&obj); return 0; } X509_OBJECT_free_contents(&obj); } /* * Try to retrieve a CRL corresponding to the _issuer_ of * the current certificate in order to check for revocation. */ memset((char *)&obj, 0, sizeof(obj)); X509_STORE_CTX_init(&store_ctx, crl_store, NULL, NULL); rc = X509_STORE_get_by_subject(&store_ctx, X509_LU_CRL, issuer, &obj); X509_STORE_CTX_cleanup(&store_ctx); crl = obj.data.crl; if (rc > 0 && crl != NULL) { /* * Check if the current certificate is revoked by this CRL */ n = sk_X509_REVOKED_num(X509_CRL_get_REVOKED(crl)); for (i = 0; i < n; i++) { revoked = sk_X509_REVOKED_value(X509_CRL_get_REVOKED(crl), i); if (ASN1_INTEGER_cmp(revoked->serialNumber, X509_get_serialNumber(xs)) == 0) { serial = ASN1_INTEGER_get(revoked->serialNumber); cp = x509_name_oneline(issuer, NULL, 0); fprintf(stderr, "Certificate with serial %ld (0x%lX) revoked per CRL from issuer %s\r\n", serial, serial, cp ? cp : "(ERROR)"); if (cp) free(cp); X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REVOKED); X509_OBJECT_free_contents(&obj); return 0; } } X509_OBJECT_free_contents(&obj); } return ok; } static void print_x509_v_error(int error) { switch (error) { case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: fprintf(ttyout, "WARNING: Server's certificate is self signed.\r\n"); break; case X509_V_ERR_CERT_HAS_EXPIRED: fprintf(ttyout, "WARNING: Server's certificate has expired.\r\n"); break; case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: fprintf(ttyout, "WARNING: Server's certificate issuer's certificate isn't available locally.\r\n"); break; case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: fprintf(ttyout, "WARNING: Unable to verify leaf signature.\r\n"); break; case X509_V_ERR_CERT_REVOKED: fprintf(ttyout, "WARNING: Certificate revoked.\r\n"); break; case X509_V_ERR_INVALID_CA: fprintf(ttyout, "WARNING: Invalid CA.\r\n"); break; case X509_V_ERR_CRL_HAS_EXPIRED: fprintf(ttyout, "WARNING: CRL has expired.\r\n"); break; case X509_V_ERR_CERT_UNTRUSTED: fprintf(ttyout, "WARNING: Certificate is untrusted.\r\n"); break; default: fprintf(ttyout, "WARNING: Error %d while verifying server's certificate.\r\n", error); break; } } static int verify_callback(int ok, X509_STORE_CTX *ctx) { int prev_error = 0; /*fprintf(stderr, "depth = %d, error = %d, ok = %d\n", ctx->error_depth, ctx->error, ok);*/ /* TODO: Make up my mind on what errors to accept or not. */ #ifdef PR_OBSD_FTP /* we can configure the client to skip the peer's cert verification */ if (tls_no_verify) return 1; #endif /* PR_OBSD_FTP */ if (!ok) { verify_error_flag = 1; print_x509_v_error(prev_error = ctx->error); } /* since the CRL check isn't included in the OpenSSL automatic certificate * check, we must call verify_crl() after we first check what errors the * automatic check might have found, otherwise they might be lost. */ ok = verify_crl(ok, ctx); if (!ok) { verify_error_flag = 1; if (ctx->error != prev_error) print_x509_v_error(ctx->error); } ok = 1; return ok; } static int seed_PRNG(void) { char stackdata[1024]; static char rand_file[300]; FILE *fh; #if OPENSSL_VERSION_NUMBER >= 0x00905100 if (RAND_status()) return 0; /* PRNG already good seeded */ #endif /* if the device '/dev/urandom' is present, OpenSSL uses it by default. * check if it's present, else we have to make random data ourselfs. */ if ((fh = fopen("/dev/urandom", "r"))) { fclose(fh); return 0; } if (RAND_file_name(rand_file, sizeof(rand_file))) tls_rand_file = rand_file; else return 1; if (!RAND_load_file(rand_file, 1024)) { /* no .rnd file found, create new seed */ unsigned int c; c = time(NULL); RAND_seed(&c, sizeof(c)); c = getpid(); RAND_seed(&c, sizeof(c)); RAND_seed(stackdata, sizeof(stackdata)); } #if OPENSSL_VERSION_NUMBER >= 0x00905100 if (!RAND_status()) return 2; /* PRNG still badly seeded */ #endif return 0; } #ifdef TLS_SESSION_FILE_CACHE static char *sfc_filename = NULL; static char *peer_ip_as_string(int sock) { #ifdef INET6 struct sockaddr_storage saddr; #else struct sockaddr_in saddr; #endif /* !INET6 */ int saddr_len = sizeof(saddr); static char rv[50]; if (getpeername(sock, (struct sockaddr *) &saddr, &saddr_len) != 0) return NULL; switch (((struct sockaddr *)&saddr)->sa_family) { case AF_INET: snprintf(rv, sizeof(rv), "%08X", htonl(((struct sockaddr_in *)&saddr)->sin_addr.s_addr)); break; #ifdef INET6 case AF_INET6: snprintf(rv, sizeof(rv), "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[0], ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[1], ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[2], ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[3], ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[4], ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[5], ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[6], ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[7], ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[8], ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[9], ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[10], ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[11], ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[12], ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[13], ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[14], ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr[15]); break; #endif /* INET6 */ default: return NULL; } return rv; } static char *make_sfc_filename(int sock) { DIR *dir; char path[MAXPATHLEN], *home, *peer; static char filename[MAXPATHLEN]; home = getenv("HOME"); if (home == NULL) return NULL; snprintf(path, sizeof(path), "%s/.tls_sfc", home); dir = opendir(path); /* if the ~/.tls_sfc dir doesn't exist, we consider this function disabled */ if (dir == NULL) return NULL; closedir(dir); /* the file name is based on the hexadecimal representation of the peer's * ip address, with `oss' (from `OpenSsl Session') as a prefix. * example: connected to 127.0.0.1 -> `oss7F000001' */ if ((peer = peer_ip_as_string(sock)) == NULL) return NULL; snprintf(filename, sizeof(filename), "%s/oss%s", path, peer); return filename; } static int session_timed_out(SSL_SESSION *s) { if (SSL_SESSION_get_time(s) + SSL_SESSION_get_timeout(s) < time(NULL)) return 1; else return 0; } int tls_sfc_client_load(SSL *s) { FILE *file; if (s == NULL) return 1; sfc_filename = make_sfc_filename(net); if (sfc_filename == NULL) return 2; file = fopen(sfc_filename, "r"); if (file) { SSL_SESSION *sess; fchmod(fileno(file), S_IRUSR | S_IWUSR); sess = PEM_read_SSL_SESSION(file, NULL, NULL, NULL); if (sess) { /* ``refresh'' the session timeout */ /*XXX SSL_SESSION_set_time(sess, time(NULL)); */ if (session_timed_out(sess)) unlink(sfc_filename); else SSL_set_session(s, sess); /* dec the ref counter in sess so it will eventually be freed */ SSL_SESSION_free(sess); } fclose(file); } return 0; } int tls_sfc_client_save(SSL *s) { FILE *file; if (s == NULL) return 1; if (sfc_filename == NULL) return 2; file = fopen(sfc_filename, "w"); if (file) { SSL_SESSION *sess; sess = SSL_get_session(s); if (sess) PEM_write_SSL_SESSION(file, sess); fclose(file); } return 0; } #endif /* TLS_SESSION_FILE_CACHE */ int tls_init(void) { int err; #ifndef PR_OBSD_FTP ttyout = stderr; #endif /* !PR_OBSD_FTP */ #ifdef DEBUG_OPENSSL_MEM CRYPTO_malloc_debug_init(); CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON); #endif /* DEBUG_OPENSSL_MEM */ SSL_load_error_strings(); SSL_library_init(); #ifdef ZLIB { COMP_METHOD *cm = COMP_zlib(); if (!tls_nozlib && cm != NULL && cm->type != NID_undef) { SSL_COMP_add_compression_method(0xe0, cm); /* Eric Young's ZLIB ID */ } } #endif /* ZLIB */ ssl_ctx = SSL_CTX_new(tls_method); if (!ssl_ctx) { fprintf(stderr, "SSL_CTX_new() %s\r\n", (char *)ERR_error_string(ERR_get_error(), NULL)); return 1; } SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2); SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, verify_callback); /* set up the CApath if defined */ if (tls_capath_dir || tls_cafile_file) { printf("Setting verify path \n"); if (!SSL_CTX_load_verify_locations(ssl_ctx, tls_cafile_file, tls_capath_dir)) { fprintf(stderr,"WARNING: can't set CApath/CAfile verify locations\n"); } } else SSL_CTX_set_default_verify_paths(ssl_ctx); #ifdef PR_OBSD_FTP /* set up session caching */ SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_CLIENT); SSL_CTX_set_session_id_context(ssl_ctx, (const unsigned char *) "1", 1); #endif /* PR_OBSD_FTP */ /* set up the CRL */ if ((tls_crl_file || tls_crl_dir) && (crl_store = X509_STORE_new())) X509_STORE_load_locations(crl_store, tls_crl_file, tls_crl_dir); #ifdef PR_TELNET ssl = SSL_new(ssl_ctx); if (!ssl) { fprintf(stderr, "SSL_new() %s\r\n", (char *)ERR_error_string(ERR_get_error(), NULL)); return 5; } SSL_set_cipher_list(ssl, tls_cipher_list); SSL_set_fd(ssl, net); #endif /* PR_TELNET */ #ifdef TLS_SESSION_FILE_CACHE /* I would love to hear the story on why I must use the * SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG option to get this working... */ SSL_set_options(ssl, SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG); tls_sfc_client_load(ssl); #endif /* TLS_SESSION_FILE_CACHE */ if (seed_PRNG()) fprintf(stderr, "Wasn't able to properly seed the PRNG!\r\n"); return 0; } static int show_hostname_warning(char *s1, char *s2) { char inp; fprintf(stderr, "WARNING: Hostname (\"%s\") and server's certificate (\"%s\") don't match, continue? (Y/N) ", s1, s2); inp = read_char(); if (!( inp == 'y' || inp == 'Y' )) return 1; else return 0; } static int star_stricmp(const char *str, const char *star_str) /* wildcard compares string `str' with a pattern string `star_str' which may * contain ONE wildcard star, '*', case-insensitive. there's probably better * ways to do this... */ { char *str_copy = strdup(str); char *star_str_copy = strdup(star_str); char *star; int rv = -1; if (str_copy == NULL || star_str_copy == NULL) goto eject; star = strchr(star_str_copy, '*'); if (star) { int str_len = strlen(str_copy); int star_str_len = strlen(star_str_copy); int star_idx = star - star_str_copy; /* first check a few special cases */ if (star_str_len > str_len + 1) /* `star_str' is too long to ever match */ goto eject; else if (star_str_len == str_len + 1) { if (*star_str_copy == '*') /* possible "*foo" == "foo" case */ memmove(star_str_copy, star_str_copy + 1, star_str_len); else if (star_str_copy[star_str_len - 1] == '*') /* possible "foo*" == "foo" case */ star_str_copy[star_str_len - 1] = '\0'; else { /* possible "f*oo" == "foo" case */ memmove(star_str_copy + star_idx, star_str_copy + star_idx + 1, star_str_len - star_idx); } } else { int diff = str_len - star_str_len; /* remove the chars from `str_copy' that the star ``wildcards'' */ memmove(str_copy + star_idx, str_copy + star_idx + diff + 1, str_len - star_idx - diff); /* remove the '*' from `star_str_copy' */ memmove(star_str_copy + star_idx, star_str_copy + star_idx + 1, star_str_len - star_idx); } } rv = strcasecmp(str_copy, star_str_copy); eject: if (str_copy) free(str_copy); if (star_str_copy) free(star_str_copy); return rv; } static int dNSName_cmp(const char *host, const char *dNSName) { int c1 = 0, c2 = 0, num_comp, rv = -1; char *p, *p1, *p2, *host_copy, *dNSName_copy; /* first we count the number of domain name components in both parameters. * they should be equal many, or it's not a match */ p = (char *) host; while ((p = strchr(p, '.'))) { c1++; p++; } p = (char *) dNSName; while ((p = strchr(p, '.'))) { c2++; p++; } if (c1 != c2) return -1; num_comp = c1 + 1; host_copy = strdup(host); dNSName_copy = strdup(dNSName); if (host_copy == NULL || dNSName_copy == NULL) goto eject; /* make substrings by replacing '.' with '\0' */ p = dNSName_copy; while ((p = strchr(p, '.'))) { *p = '\0'; p++; } p = host_copy; while ((p = strchr(p, '.'))) { *p = '\0'; p++; } /* compare each component */ p1 = host_copy; p2 = dNSName_copy; for (; num_comp; num_comp--) { if (star_stricmp(p1, p2)) /* failed match */ goto eject; p1 += strlen(p1) + 1; p2 += strlen(p2) + 1; } /* match ok */ rv = 0; eject: if (dNSName_copy) free(dNSName_copy); if (host_copy) free(host_copy); return rv; } #ifndef INADDR_NONE #define INADDR_NONE -1 #endif /* !INADDR_NONE */ static int check_server_name(SSL *s) /* returns 0 if hostname and server's cert matches, else 1 */ { char **dNSName, *commonName; unsigned char **ipAddress; struct in_addr ia; /* first we check if `tls_hostname' is in fact an ip address */ if ((ia.s_addr = inet_addr(tls_hostname)) != INADDR_NONE) { ipAddress = (unsigned char **) tls_get_SAN_objs(s, GEN_IPADD); if (ipAddress) { int i = 0, rv; char *server_ip = "UNKNOWN"; for (i = 0; ipAddress[i]; i++) if (*(unsigned long *)ipAddress[i] == ia.s_addr) return 0; if (ipAddress[i - 1]) { ia.s_addr = *(unsigned long *)ipAddress[i - 1]; server_ip = inet_ntoa(ia); } rv = show_hostname_warning(tls_hostname, server_ip); for (i = 0; ipAddress[i]; i++) free(ipAddress[i]); return rv; } else return show_hostname_warning(tls_hostname, "NO IP IN CERT"); } /* look for dNSName(s) in subjectAltName in the server's certificate */ dNSName = tls_get_SAN_objs(s, GEN_DNS); if (dNSName) { int i = 0, rv; for (i = 0; dNSName[i]; i++) { if (!dNSName_cmp(tls_hostname, dNSName[i])) return 0; } rv = show_hostname_warning(tls_hostname, dNSName[i - 1] ? dNSName[i - 1] : "UNKNOWN"); for (i = 0; dNSName[i]; i++) free(dNSName[i]); return rv; } else if ((commonName = tls_get_commonName(s))) { /* so the server didn't have any dNSName's, check the commonName */ if (!dNSName_cmp(tls_hostname, commonName)) return 0; else return show_hostname_warning(tls_hostname, commonName); } else return 1; } static char *file_fullpath(char *fn) { static char fp[256]; FILE *file; char *dir; /* check if it is a full path already */ if (strchr(fn, '/')) return fn; /* check if it is in current dir */ if ((file = fopen(fn, "r"))) { fclose(file); return fn; } if (!(dir = getenv(X509_get_default_cert_dir_env()))) /* $SSL_CERT_DIR */ dir = (char *)X509_get_default_cert_dir(); snprintf(fp, sizeof(fp), "%s/%s", dir, fn); if ((file = fopen(fp, "r"))) { fclose(file); return fp; } dir = (char *)X509_get_default_private_dir(); snprintf(fp, sizeof(fp), "%s/%s", dir, fn); if ((file = fopen(fp, "r"))) { fclose(file); return fp; } return fn; /* here fn is proven wrong, but we return it anyway */ } #ifdef PR_TELNET void tls_shutdown(void) { if (tls_active) { SSL_shutdown(ssl); tls_active = 0; } } int tls_connect(void) { int err; char *subject, *issuer, *subjectAltName, inp; /* let's see if we are going to use any client certificate */ x509rc_read_filenames(); if (tls_cert_file) { int mode; char *key_file = tls_key_file; if (!key_file) key_file = tls_cert_file; err = SSL_use_certificate_file(ssl, file_fullpath(tls_cert_file), SSL_FILETYPE_PEM); if (err <= 0) { fprintf(stderr, "SSL_use_certificate_file(\"%s\") %s\r\n", file_fullpath(tls_cert_file), (char *)ERR_error_string(ERR_get_error(), NULL)); return 1; } /* we must add MODE_EDIT for any password reading routine, or CR won't * terminate the line */ mode = getconnmode(); TerminalNewMode(mode | MODE_EDIT); err = SSL_use_PrivateKey_file(ssl, file_fullpath(key_file), SSL_FILETYPE_PEM); TerminalNewMode(mode); if (err <= 0) { fprintf(stderr, "SSL_use_PrivateKey_file(\"%s\") %s\r\n", file_fullpath(key_file), (char *)ERR_error_string(ERR_get_error(), NULL)); return 2; } if (!SSL_check_private_key(ssl)) { fprintf(stderr, "Private key don't match the certificate public key!\r\n"); return 3; } } /* it seems SSL_connect() don't like non-blocking sockets, or...? */ NetNonblockingIO(net, 0); fprintf(stderr, "[Negotiating SSL/TLS session... ]\r\n"); err = SSL_connect(ssl); NetNonblockingIO(net, 1); if (err == 1) { if (verify_error_flag) { fprintf(stderr, "WARNING: Errors while verifying the server's certificate chain, continue? (Y/N) "); inp = read_char(); if (!( inp == 'y' || inp == 'Y' )) quit(); } tls_active = 1; if (subject = tls_get_subject_name(ssl)) fprintf(stderr, "[Subject: %s]\r\n", subject); else { fprintf(stderr, "WARNING: Server didn't provide a certificate, continue? (Y/N) "); inp = read_char(); if (!( inp == 'y' || inp == 'Y' )) quit(); } if ((subjectAltName = x509v3_subjectAltName_oneline(ssl, NULL, 0))) { fprintf(stderr, "[X509v3 Subject Alternative Name: %s]\r\n", subjectAltName); free(subjectAltName); } if (issuer = tls_get_issuer_name(ssl)) fprintf(stderr, "[Issuer: %s]\r\n", issuer); fprintf(stderr, "[Cipher: %s (%d bits)]\r\n", SSL_get_cipher(ssl), SSL_get_cipher_bits(ssl, NULL)); if (ssl->expand && ssl->expand->meth) fprintf(stderr, "Compression: %s\r\n", ssl->expand->meth->name); if (check_server_name(ssl)) { /* the host name on the command line didn't match with the server's * cert, and the user didn't ansver `Y' to the question. */ quit(); } return 0; } else { /* TLS connection failed */ fprintf(stderr, "SSL_connect() = %d, %s\r\n", err, (char *)ERR_error_string(ERR_get_error(), NULL)); tls_shutdown(); tls_cleanup(); return 5; } return 6; } int tls_pending(void) { if (tls_active) return SSL_pending(ssl); else return 0; } #endif /* PR_TELNET */ #ifdef PR_OBSD_FTP int tls_connect_ctrl(int s) { int err; char *subject, *issuer, *commonName, *dNSName, *subjectAltName, inp; if (ctrl_conn.ssl) { fprintf(ttyout, "Already TLS connected!\r\n"); return 1; } ctrl_conn.ssl = SSL_new(ssl_ctx); if (!ctrl_conn.ssl) { fprintf(ttyout, "SSL_new() %s\r\n", (char *)ERR_error_string(ERR_get_error(), NULL)); return 2; } SSL_set_cipher_list(ctrl_conn.ssl, tls_cipher_list); SSL_set_fd(ctrl_conn.ssl, s); ctrl_conn.sock = s; /* let's see if we are going to use any client certificate */ x509rc_read_filenames(); if (tls_cert_file) { char *key_file = tls_key_file; if (!key_file) key_file = tls_cert_file; err = SSL_use_certificate_file(ctrl_conn.ssl, file_fullpath(tls_cert_file), SSL_FILETYPE_PEM); if (err <= 0) { fprintf(stderr, "SSL_use_certificate_file(\"%s\") %s\r\n", file_fullpath(tls_cert_file), (char *)ERR_error_string(ERR_get_error(), NULL)); return 3; } err = SSL_use_PrivateKey_file(ctrl_conn.ssl, file_fullpath(key_file), SSL_FILETYPE_PEM); if (err <= 0) { fprintf(stderr, "SSL_use_PrivateKey_file(\"%s\") %s\r\n", file_fullpath(key_file), (char *)ERR_error_string(ERR_get_error(), NULL)); return 4; } if (!SSL_check_private_key(ctrl_conn.ssl)) { fprintf(stderr, "Private key don't match the certificate public key!\r\n"); return 5; } } fprintf(ttyout, "[Starting SSL/TLS negotiation...]\r\n"); err = SSL_connect(ctrl_conn.ssl); if (err == 1) { if (verify_error_flag) { fprintf(ttyout, "WARNING: Errors while verifying the server's certificate chain, continue? (Y/N) "); inp = read_char(); if (!( inp == 'y' || inp == 'Y' )) return 3; } if ((subject = tls_get_subject_name(ctrl_conn.ssl))) fprintf(ttyout, "[Subject: %s]\r\n", subject); else { fprintf(ttyout, "WARNING: Server didn't provide a certificate, continue? (Y/N) "); inp = read_char(); if (!( inp == 'y' || inp == 'Y' )) return 4; } if ((subjectAltName = x509v3_subjectAltName_oneline(ctrl_conn.ssl, NULL, 0))) { fprintf(ttyout, "[X509v3 Subject Alternative Name: %s]\r\n", subjectAltName); free(subjectAltName); } if ((issuer = tls_get_issuer_name(ctrl_conn.ssl))) fprintf(ttyout, "[Issuer: %s]\r\n", issuer); fprintf(ttyout, "[Cipher: %s (%d bits)]\r\n", SSL_get_cipher(ctrl_conn.ssl), SSL_get_cipher_bits(ctrl_conn.ssl, NULL)); if (ctrl_conn.ssl->expand && ctrl_conn.ssl->expand->meth) fprintf(stderr, "Compression: %s\r\n", ctrl_conn.ssl->expand->meth->name); if (check_server_name(ctrl_conn.ssl)) { /* the host name on the command line didn't match with the server's * cert, and the user didn't ansver `Y' to the question. */ return 5; } return 0; } else { /* TLS connection failed */ fprintf(ttyout, "SSL_connect() = %d, %s\r\n", err, (char *)ERR_error_string(ERR_get_error(), NULL)); SSL_shutdown(ctrl_conn.ssl); SSL_free(ctrl_conn.ssl); ctrl_conn.ssl = NULL; ctrl_conn.sock = -1; return 6; } } int tls_connect_data(int s) { int err; if (data_conn.ssl) { fprintf(ttyout, "Already TLS connected!\r\n"); return 1; } data_conn.ssl = SSL_new(ssl_ctx); if (!data_conn.ssl) { fprintf(ttyout, "SSL_new() %s\r\n", (char *)ERR_error_string(ERR_get_error(), NULL)); return 2; } /* * if we have cleared the control connection back to * plaintext (with CCC) then we need to pop the * cloned copy instead of copying the, now missing, * control connection * N.B. if both ctrl_conn.ssl and ctrl_ssl_clone are NULL * then something is broken. */ if((NULL == ctrl_conn.ssl) && (NULL != ctrl_ssl_clone)) { SSL_copy_session_id(data_conn.ssl, ctrl_ssl_clone); } else { SSL_copy_session_id(data_conn.ssl, ctrl_conn.ssl); } SSL_set_fd(data_conn.ssl, s); data_conn.sock = s; err = SSL_connect(data_conn.ssl); if (err != 1) { /* TLS connection failed */ fprintf(ttyout, "SSL_connect() = %d, %s\r\n", err, (char *)ERR_error_string(ERR_get_error(), NULL)); SSL_shutdown(data_conn.ssl); SSL_free(data_conn.ssl); data_conn.ssl = NULL; data_conn.sock = -1; return 3; } else return 0; } static void ssl_close(SSL *ssl) { if (ssl) { if (ssl == data_conn.ssl) { int fd = SSL_get_fd(ssl); if (fd >= 0) tls_fputc_fflush(fd); /*if(NULL != ctrl_ssl_clone) { SSL_copy_session_id(ctrl_ssl_clone,data_conn.ssl); }*/ } SSL_shutdown(ssl); SSL_free(ssl); if (ssl == data_conn.ssl) { data_conn.ssl = NULL; data_conn.sock = -1; } else if (ssl == ctrl_conn.ssl) { ctrl_conn.ssl = NULL; ctrl_conn.sock = -1; } } } int tls_fclose(FILE *stream) { SSL *ssl = SOCK_TO_SSL(fileno(stream)); ssl_close(ssl); return fclose(stream); } int tls_close(int fd) { SSL *ssl = SOCK_TO_SSL(fd); ssl_close(ssl); return close(fd); } int tls_shutdown(int s, int how) { /* if s == -1, do a `global' SSL shutdown, else emulate a shutdown(2) */ if (s == -1) { ssl_close(data_conn.ssl); ssl_close(ctrl_conn.ssl); return 0; } else { SSL *ssl = SOCK_TO_SSL(s); ssl_close(ssl); return shutdown(s, how); } } void tls_free_ssls(void) { ssl_close(data_conn.ssl); ssl_close(ctrl_conn.ssl); } #endif /* PR_OBSD_FTP */ void tls_cleanup(void) { if (crl_store) { X509_STORE_free(crl_store); crl_store = NULL; } #ifdef PR_TELNET if (ssl) { #ifdef TLS_SESSION_FILE_CACHE tls_sfc_client_save(ssl); #endif /* TLS_SESSION_FILE_CACHE */ SSL_free(ssl); ssl = NULL; } #endif /* PR_TELNET */ #ifdef PR_OBSD_FTP if (data_conn.ssl) { SSL_free(data_conn.ssl); data_conn.ssl = NULL; data_conn.sock = -1; } if (ctrl_conn.ssl) { SSL_free(ctrl_conn.ssl); ctrl_conn.ssl = NULL; ctrl_conn.sock = -1; } #endif /* PR_OBSD_FTP */ if (ssl_ctx) { SSL_CTX_free(ssl_ctx); ssl_ctx = NULL; } ERR_free_strings(); ERR_remove_state(0); EVP_cleanup(); /* release the stuff allocated by SSL_library_init() */ if (tls_cert_file) { free(tls_cert_file); tls_cert_file = NULL; } if (tls_key_file) { free(tls_key_file); tls_key_file = NULL; } if (tls_crl_file) { free(tls_crl_file); tls_crl_file = NULL; } if (tls_crl_dir) { free(tls_crl_dir); tls_crl_dir = NULL; } if (tls_capath_dir) { free(tls_capath_dir); tls_capath_dir = NULL; } if (tls_cafile_file) { free(tls_cafile_file); tls_cafile_file = NULL; } if (tls_hostname) { free(tls_hostname); tls_hostname = NULL; } if (tls_cipher_list) { free(tls_cipher_list); tls_cipher_list = NULL; } if (tls_rand_file) RAND_write_file(tls_rand_file); #ifdef DEBUG_OPENSSL_MEM CRYPTO_mem_leaks_fp(stderr); #endif /* DEBUG_OPENSSL_MEM */ } static void handle_ssl_error(int error, char *where) { switch (error) { case SSL_ERROR_NONE: return; case SSL_ERROR_SSL: fprintf(ttyout, "unhandled SSL_ERROR_SSL in %s\r\n", where); break; case SSL_ERROR_WANT_READ: fprintf(ttyout, "unhandled SSL_ERROR_WANT_READ in %s\r\n", where); break; case SSL_ERROR_WANT_WRITE: fprintf(ttyout, "unhandled SSL_ERROR_WANT_WRITE in %s\r\n", where); break; case SSL_ERROR_WANT_X509_LOOKUP: fprintf(ttyout, "unhandled SSL_ERROR_WANT_X509_LOOKUP in %s\r\n", where); break; case SSL_ERROR_SYSCALL: fprintf(ttyout, "unhandled SSL_ERROR_SYSCALL in %s\r\n", where); break; case SSL_ERROR_ZERO_RETURN: fprintf(ttyout, "unhandled SSL_ERROR_ZERO_RETURN in %s\r\n", where); break; case SSL_ERROR_WANT_CONNECT: fprintf(ttyout, "unhandled SSL_ERROR_WANT_CONNECT in %s\r\n", where); break; default: fprintf(ttyout, "unhandled SSL_ERROR %d in %s\r\n", error, where); break; } #ifdef PR_OBSD_FTP tls_shutdown(-1, 0); tls_cleanup(); exit(1); #endif /* PR_OBSD_FTP */ } static int select_read(int rfd) /* timeout = 20 seconds */ { fd_set rfds; struct timeval tv; FD_ZERO(&rfds); FD_SET(rfd, &rfds); tv.tv_sec = 20; tv.tv_usec = 0; return select(rfd + 1, &rfds, NULL, NULL, &tv); } ssize_t tls_read(int fd, void *buf, size_t count) { SSL *s = SOCK_TO_SSL(fd); retry: if (s) { ssize_t c = SSL_read(s, buf, count); if (c < 0) { int err = SSL_get_error(s, c); /* read(2) returns only the generic error number -1 */ c = -1; switch (err) { case SSL_ERROR_WANT_READ: /* OpenSSL needs more data from the wire to finish the current block, * so we wait a little while for it. */ err = select_read(fd); if (err > 0) goto retry; else if (err == 0) /* still missing data after timeout, emulate an EINTR and return. */ errno = EINTR; /* if err < 0, i.e. some error from the select(), everything is already * in place; errno is properly set and this function returns -1. */ break; case SSL_ERROR_SSL: case SSL_ERROR_SYSCALL: /* the SSL connection is screwed up, emulate EOF */ c = 0; break; default: handle_ssl_error(err, "tls_read()"); break; } } return c; } else return read(fd, buf, count); } int tls_fgetc(FILE *stream) { SSL *s = SOCK_TO_SSL(fileno(stream)); if (s) { unsigned char r; int err; do err = tls_read(fileno(stream), &r, 1); while (err < 0 && errno == EINTR); if (err == 1) return (int) r; else return EOF; } else return fgetc(stream); } int tls_recv(int fd, void *buf, size_t len, int flags) { SSL *s = SOCK_TO_SSL(fd); if (s) return (int) tls_read(fd, buf, len); else return recv(fd, buf, len, flags); } ssize_t tls_write(int fd, const void *buf, size_t count) { SSL *s = SOCK_TO_SSL(fd); if (s) { ssize_t c = SSL_write(s, buf, count); if (c < 0) { int err = SSL_get_error(s, c); /* write(2) returns only the generic error number -1 */ c = -1; switch (err) { case SSL_ERROR_WANT_WRITE: /* simulate an EINTR in case OpenSSL wants to write more */ errno = EINTR; break; case SSL_ERROR_SSL: case SSL_ERROR_SYSCALL: /* the SSL connection is screwed up, emulate EPIPE */ errno = EPIPE; break; default: handle_ssl_error(err, "tls_write()"); break; } } return c; } else return write(fd, buf, count); } int tls_fputs(const char *str, FILE *stream) { SSL *s = SOCK_TO_SSL(fileno(stream)); if (s) { int sent = 0, size, w; size = strlen(str); do { w = tls_write(fileno(stream), str + sent, size - sent); if (w > 0) sent += w; else if (!(w < 0 && errno == EINTR)) break; /* other error than EINTR or w == 0 */ } while (sent != size); if (w < 0) return EOF; else return w; } else return fputs(str, stream); } void tls_fputc_fflush(int fd) { if (fputc_buflen > 0) { tls_write(fd, fputc_buffer, fputc_buflen); fputc_buflen = 0; } return; } int tls_fputc(int c, FILE *stream) { SSL *s = SOCK_TO_SSL(fileno(stream)); if (s) { unsigned char uc = c; int err = 1; do { fputc_buffer[fputc_buflen++] = uc; if (fputc_buflen >= FPUTC_BUFFERSIZE) { err = tls_write(fileno(stream), fputc_buffer, fputc_buflen); if (err >= 0) { err = 1; } fputc_buflen = 0; } } while (err < 0 && errno == EINTR); if (err == 1) return (int) uc; else return EOF; } else return fputc(c, stream); } int tls_send(int fd, const void *msg, size_t len, int flags) { SSL *s = SOCK_TO_SSL(fd); if (s) return (int) tls_write(fd, msg, len); else return send(fd, msg, len, flags); } #ifdef PR_OBSD_FTP int tls_vfprintf(FILE *stream, const char *format, va_list ap) { #define SNP_MAXBUF 1024000 SSL *ssl = SOCK_TO_SSL(fileno(stream)); if (ssl) { /* here I boldly assume that snprintf() and vsnprintf() uses the same * return value convention. if not, what kind of libc is this? ;-) */ char sbuf[1024] = { 0 }, *buf = sbuf, *lbuf = NULL; int sent = 0, size, ret, w; ret = vsnprintf(sbuf, sizeof(sbuf), format, ap); #ifdef SNPRINTF_OK /* this one returns the number of bytes it wants to write in case of overflow */ if (ret >= sizeof(sbuf) && ret < SNP_MAXBUF) { /* sbuf was too small, use a larger lbuf */ lbuf = malloc(ret + 1); if (lbuf) { vsnprintf(lbuf, ret + 1, format, ap); buf = lbuf; } } #else # ifdef SNPRINTF_HALFBROKEN /* this one returns the number of bytes written (excl. \0) in case of overflow */ # define SNP_OVERFLOW(x, y) ( x == y ? 1 : 0 ) # define SNP_NOERROR(x) ( x < 0 ? 0 : 1 ) # else # ifdef SNPRINTF_BROKEN /* this one returns -1 in case of overflow */ # define SNP_OVERFLOW(x, y) ( x < 0 ? 1 : 0 ) # define SNP_NOERROR(x) ( 1 ) /* if -1 means overflow, what's the error indication? */ # else # error No valid SNPRINTF_... macro defined! # endif /* !SNPRINTF_BROKEN */ # endif /* !SNPRINTF_HALFBROKEN */ if (SNP_NOERROR(ret) && SNP_OVERFLOW(ret, sizeof(sbuf) - 1)) { /* sbuf was too small, use a larger lbuf */ size = sizeof(sbuf); do { if ((size *= 2) > SNP_MAXBUF) /* try to double the size */ break; if (lbuf) free(lbuf); lbuf = malloc(size); if (lbuf) { ret = vsnprintf(lbuf, size, format, ap); buf = lbuf; } else break; } while (SNP_NOERROR(ret) && SNP_OVERFLOW(ret, size - 1)); } #endif /* !SNPRINTF_OK */ size = strlen(buf); do { w = tls_write(fileno(stream), buf + sent, size - sent); if (w > 0) sent += w; else if (!(w < 0 && errno == EINTR)) break; /* other error than EINTR or w == 0 */ } while (sent != size); if (lbuf) free(lbuf); return sent; } else return vfprintf(stream, format, ap); } #ifdef __STDC__ int tls_fprintf(FILE *stream, const char *fmt, ...) #else int tls_fprintf(stream, fmt, va_alist) FILE *stream; char *fmt; va_dcl #endif { va_list ap; #ifdef __STDC__ va_start(ap, fmt); #else va_start(ap); #endif return tls_vfprintf(stream, fmt, ap); } #endif /* PR_OBSD_FTP */ int tls_fflush(FILE *stream) { if (stream == NULL) return fflush(NULL); if (SOCK_TO_SSL(fileno(stream))) return 0; /* don't do anything! */ else return fflush(stream); } static char *glob_tilde(char *s) /* very simple ~ expansion */ { char *h, *r; if (s == NULL) return NULL; if (*s != '~') return s; if (!(h = getenv("HOME"))) return s; if (!(r = malloc(strlen(h) + strlen(s)))) return s; sprintf(r, "%s%s", h, s + 1); free(s); return r; } int x509rc_read_filenames(void) { char filename[MAXPATHLEN], s1[256], s2[MAXPATHLEN], s3[MAXPATHLEN], line[sizeof(s1) + sizeof(s2) + sizeof(s3) + 50], format[50], *home = NULL, *p, *host = NULL; int rv = 0; FILE *file; if (x509rc_override) /* cert already specified on command line */ return 0; if (!tls_hostname) return 1; host = strdup(tls_hostname); if (host == NULL) return 2; for (p = host; *p; p++) *p = tolower(*p); home = getenv("HOME"); if (home == NULL) return 3; snprintf(filename, sizeof(filename), "%s/.x509rc", home); file = fopen(filename, "r"); if (file == NULL) return 4; /* create the sscanf() format string */ snprintf(format, sizeof(format), "%%%us %%%us %%%us", (unsigned int) sizeof(s1) - 1, (unsigned int) sizeof(s2) - 1, (unsigned int) sizeof(s3) - 1); while (fgets(line, sizeof(line), file)) { int c; if ((p = strchr(line, '#'))) /* truncate at comment */ *p = 0; c = sscanf(line, format, &s1, &s2, &s3); if (c < 2) continue; for (p = s1; *p; p++) *p = tolower(*p); /* check for exact host name match */ if (!strcmp(s1, host)) { if (tls_cert_file) free(tls_cert_file); tls_cert_file = glob_tilde(strdup(s2)); if (c > 2) { if (tls_key_file) free(tls_key_file); tls_key_file = glob_tilde(strdup(s3)); } goto cleanup; } /* check for a "prefix" match */ if (*s1 == '.') { int hlen = strlen(host); int slen = strlen(s1); if (hlen > slen + 1 && !strcmp(s1 + 1, host + hlen - slen + 1)) { if (tls_cert_file) free(tls_cert_file); tls_cert_file = glob_tilde(strdup(s2)); if (c > 2) { if (tls_key_file) free(tls_key_file); tls_key_file = glob_tilde(strdup(s3)); } } } } cleanup: fclose(file); if (host) free(host); return rv; } void tls_ccc( void ) { int rc; int shutcount = 0; /* * we can only do one CCC, if we do do it, then we need to push the * current state of the control connection to allow us to clone * it later for data connections */ ctrl_ssl_clone = SSL_new(ssl_ctx); SSL_copy_session_id(ctrl_ssl_clone, ctrl_conn.ssl); rc = SSL_shutdown(ctrl_conn.ssl); /* * the rc of 0 from SSL_shutdown means - "call it again" * (this from the doc on www.openssl.org and not the * man pages :-( ) * Just put a count around it to make sure we don't hang */ while((0 == rc) && (shutcount < 5)) { rc = SSL_shutdown(ctrl_conn.ssl); shutcount++; } if (rc != 1) { fprintf(stderr, "SSL shutdown failed after CCC. Session may be unusable\n"); } /* * These just ensure that we don't think that we have SSL on the * control connection any more */ SSL_free(ctrl_conn.ssl); ctrl_conn.ssl = NULL; ctrl_conn.sock = -1; return; }