From c235c6ac130cbe405445576481ece795c1d7b800 Mon Sep 17 00:00:00 2001 From: Ian Kelling Date: Tue, 13 Oct 2020 17:16:15 -0400 Subject: [PATCH] 5.0 from trustcommerce --- Makefile.in | 2 +- README | 51 +- config.m4 | 2 +- curltest.php | 4 +- mem.c | 166 ---- openssl_management.c | 64 ++ php_tclink.c | 2 +- tclink.c | 1723 ++++++++++++++++++++++-------------------- tclink.h | 71 +- tctest.php | 5 +- validate.c | 251 +++--- 11 files changed, 1154 insertions(+), 1187 deletions(-) delete mode 100644 mem.c create mode 100644 openssl_management.c diff --git a/Makefile.in b/Makefile.in index a137c97..209d626 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,7 +1,7 @@ # $Id: Makefile.in,v 1.5.10.1 2015-10-15 23:20:29 mlai Exp $ LTLIBRARY_NAME = libtclink.la -LTLIBRARY_SOURCES = tclink.c php_tclink.c mem.c validate.c +LTLIBRARY_SOURCES = tclink.c php_tclink.c validate.c openssl_management.c LTLIBRARY_SHARED_NAME = tclink.la LTLIBRARY_SHARED_LIBADD = $(TCLINK_SHARED_LIBADD) diff --git a/README b/README index e4f323c..4b7ff75 100644 --- a/README +++ b/README @@ -3,20 +3,20 @@ like the ones embedded in TCLink. It may be unlawful to download TCLink in these countries. - TCLink v4.2.1 + TCLink v4.5.0 PHP Implementation - copyright (C) TrustCommerce 2016 + copyright (C) TrustCommerce 2019-2020 http://www.trustcommerce.com techsupport@trustcommerce.com - September 13, 2016 + February 17, 2020 I. DESCRIPTION - TCLink is a thin client library to allow your e-commerce servers to + TCLink is a thin client library to allow your servers to connect to the TrustCommerce payment gateway easily and consistently. The protocol (which is the same across all platforms and languages) is -well-documented in the Web Developer's Guide, so please consult it for +well-documented in the TC Link API Developer Guide, so please consult it for any questions you may have about the protocol syntax itself. If you are on a Windows environment, do not use this client. Please @@ -55,17 +55,22 @@ IV. BUILDING phpize - Afterwards, type the following commands: + Afterwards, type the following commands, distribution specific comments provided when available: - ./configure --with-ssl +General Instructions: + ./configure --with-ssl=[directory containing libssl.so] make - Note 1: On some systems, the SSL library may be in a different place, such as /usr/lib64. - In that case, type the following commands instead: +Debian Jessie: +Debian Stretch: +Debian Buster: + ./configure --with-ssl=/usr/lib/x86_64-linux-gnu + make +Centos 8.1.1911: ./configure --with-ssl=/usr/lib64 make - + If you are not sure where your SSL library is installed, contact your system administrator or hosting provider for more information. @@ -76,16 +81,15 @@ IV. BUILDING This script will run a test transaction and print the results. - V. INSTALLATION If you have root access to the machine, you will probably want to install TCLink as a global extension. You can do this by copying the module library (modules/tclink.so) to your PHP extensions directory -(typically /usr/lib/php5). Your extensions directory is defined by the +(typically /usr/lib/php*). Your extensions directory is defined by the "extension_dir" directive in php.ini. - Edit php.ini (typically found in /etc) and add the following line: + Edit php.ini (typically found somewhere in /etc) and add the following line: extension=tclink.so @@ -93,18 +97,31 @@ module library (modules/tclink.so) to your PHP extensions directory If you can't or don't want to install the module system wide, you can still load it manually in each script that needs to access it through -the dl() call. See the top of tctest.php for an example. +the dl() call. Your system must be configured to allow dynamic loading of +modules for this to work; see your system administrator or hosting provider. VI. USAGE The tctest.php script shows a simple example of running transactions through the TCLink API. A more complex example is contained in -tcexample.php. For further information, please consult the TC -Developer's Guide, located in the doc subdirectory. +tcexample.php. The curltest.php script shows a simple example of running transactions -through the HTTPS post, as describd in the Developer's Guide. It is +through the HTTPS post, as described in the TC Link API Developer Guide. It is provided here in as a conveinence to users who are unable to install TCLink and have Curl available in their installation. +VII. PLATFORMS + + The included code has been tested on the following platforms: + +CentOS Linux release 8.1.1911 (Core) + OpenSSL Version 1.1.1c-2 (Distribution), php-cli-7.2.11-2.module_el8.1.0+209+03b9a8ff.x86_64 (Distribution) +Debian Linux release 8.11 + OpenSSL Version 1.0.1t-1+deb8u12 (Distribution), PHP version 5.6.40+dfsg-0+deb8u8 (Distribution) +Debian Linux release 9.8 + OpenSSL Version 1.1.0l-1~deb9u1 (Distribution), PHP version 7.0.33-0+deb9u6 (Distribution) +Debian Linux release 10.3 + OpenSSL Version 1.1.1d-0+deb10u2 (Distribution), PHP version 7.3.11-1~deb10u1 (Distribution) + diff --git a/config.m4 b/config.m4 index fc9447e..8a2e222 100644 --- a/config.m4 +++ b/config.m4 @@ -84,7 +84,7 @@ fi TCLINK_SHARED_LIBADD="-lssl -lcrypto -L$SSL_DIR"; CFLAGS="-I$SSL_DIR/../include" -TCLINK_VERSION="4.2.1-PHP-`uname -sm | tr ' ' -`" +TCLINK_VERSION="4.5.0-PHP-`uname -sm | tr ' ' -`" AC_DEFINE_UNQUOTED([TCLINK_VERSION], "$TCLINK_VERSION", [TCLink version string.]) AC_MSG_NOTICE([CA path: $TCLINK_CA_PATH]) diff --git a/curltest.php b/curltest.php index de2044b..6974bd4 100644 --- a/curltest.php +++ b/curltest.php @@ -1,4 +1,4 @@ - 'preauth', 'amount' => '100', 'cc' => '4111111111111111', - 'exp' => '1213', + 'exp' => '0429', 'address1' => '123 Anywhere St', 'zip' => '92606' ); diff --git a/mem.c b/mem.c deleted file mode 100644 index 95c6be5..0000000 --- a/mem.c +++ /dev/null @@ -1,166 +0,0 @@ -/* originally from crypto/x509/by_file.c */ -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, 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. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - -/* Added code to use a memory object as opposed to a file for lookup purposes. */ - -#include - -#include -#include -#include -#include -#include - -static int by_mem_ctrl(X509_LOOKUP *, int, const char *, long, char **); -static int X509_load_cert_crl_mem(X509_LOOKUP *ctx, const char *str, int type); - -X509_LOOKUP_METHOD x509_mem_lookup = -{ - "Memory from user provided string", - NULL, - NULL, - NULL, - NULL, - by_mem_ctrl, - NULL, - NULL, - NULL, - NULL -}; - -static X509_LOOKUP_METHOD * X509_LOOKUP_mem() -{ - return (&x509_mem_lookup); -} - -static int by_mem_ctrl(X509_LOOKUP *ctx, int cmd, const char * str, long argl, char ** ret) -{ - int status = 0; - if (cmd == X509_L_FILE_LOAD) - { - if (argl == X509_FILETYPE_PEM) - return X509_load_cert_crl_mem(ctx, str, X509_FILETYPE_PEM) != 0; - } - return status; -} - -static int X509_load_cert_crl_mem(X509_LOOKUP *ctx, const char *str, int type) -{ - BIO *in = NULL; - int status = 0; - int count = 0; - int i; - X509 *x = NULL; - - if (str == NULL) return 1; - in = BIO_new(BIO_s_mem()); - if ((in == NULL) || (BIO_write(in, str, strlen(str)) != strlen(str)) || type != X509_FILETYPE_PEM) - { - /* this error isn't the same as the real file one, but it'll serve the same purpose */ - X509err(X509_F_X509_LOAD_CERT_FILE,ERR_R_SYS_LIB); - goto err; - } - - for (;;) - { - x=PEM_read_bio_X509_AUX(in,NULL,NULL,NULL); - if (x == NULL) - { - if ((ERR_GET_REASON(ERR_peek_last_error()) == - PEM_R_NO_START_LINE) && (count > 0)) - { - ERR_clear_error(); - break; - } - else - { - X509err(X509_F_X509_LOAD_CERT_FILE, - ERR_R_PEM_LIB); - goto err; - } - } - i=X509_STORE_add_cert(ctx->store_ctx,x); - if (!i) goto err; - count++; - X509_free(x); - x=NULL; - } - status=count; - -err: - if (x != NULL) X509_free(x); - if (in != NULL) BIO_free(in); - return status; - -} - -static int X509_STORE_load_mem(X509_STORE *ctx, const char *str) -{ - X509_LOOKUP *lookup; - if (!str) return 1; - - lookup = X509_STORE_add_lookup(ctx, X509_LOOKUP_mem()); - if (lookup == NULL) return 0; - if (X509_LOOKUP_ctrl(lookup,X509_L_FILE_LOAD,str,X509_FILETYPE_PEM, NULL) != 1) - return 0; - return 1; -} -int SSL_CTX_load_verify_locations_mem(SSL_CTX * ctx, const char *str) -{ - return X509_STORE_load_mem(ctx->cert_store, str); -} diff --git a/openssl_management.c b/openssl_management.c new file mode 100644 index 0000000..bc36263 --- /dev/null +++ b/openssl_management.c @@ -0,0 +1,64 @@ +/** + * PHP doesn't support user land threads but we still have to initialize + * the library. + * + * Use following link for additional details: + * 'https://www.openssl.org/docs/man1.0.1/crypto/threads.html' + * 'https://wiki.openssl.org/index.php/Library_Initialization' + + */ + +#include +#include +#include +#include + +void __attribute__((constructor)) TCLink_OpenSSLInit(void); +void __attribute__((destructor)) TCLink_OpenSSLCleanup(void); + +/** + * + * Initialize the OpenSSL library. + * Also sets up static callback functions required for multi-thread safety. + */ +void +TCLink_OpenSSLInit(void) +{ + int ret; +#if OPENSSL_VERSION_NUMBER < 0x10100000L + SSL_load_error_strings(); + ret = SSL_library_init(); + assert(ret == 1); +#else + ret = OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CRYPTO_STRINGS | + OPENSSL_INIT_ADD_ALL_CIPHERS | + OPENSSL_INIT_ADD_ALL_DIGESTS | + OPENSSL_INIT_LOAD_CONFIG | OPENSSL_INIT_ASYNC | +#ifdef OPENSSL_INIT_NO_ATEXIT + OPENSSL_INIT_NO_ATEXIT | +#endif +#ifdef OPENSSL_INIT_ATFORK + OPENSSL_INIT_ATFORK | +#endif + OPENSSL_INIT_LOAD_SSL_STRINGS, + NULL); + assert(ret == 1); +#endif +} + +/** + * + * De-initializes the OpenSSL library. + * Performs cleanup required for global data structures. + */ +void +TCLink_OpenSSLCleanup(void) +{ +#if OPENSSL_VERSION_NUMBER < 0x10100000L + EVP_cleanup(); + CRYPTO_cleanup_all_ex_data(); + ERR_free_strings(); +#else + OPENSSL_cleanup(); +#endif +} diff --git a/php_tclink.c b/php_tclink.c index 08756f6..5590fcf 100644 --- a/php_tclink.c +++ b/php_tclink.c @@ -1,7 +1,7 @@ /* * TCLink PHP Module * - * TCLink Copyright (c) 2010 TrustCommerce. + * TCLink Copyright (c) 2010-2020 TrustCommerce. * http://www.trustcommerce.com * techsupport@trustcommerce.com * (949) 387-3747 diff --git a/tclink.c b/tclink.c index 9510798..e0a6c4b 100644 --- a/tclink.c +++ b/tclink.c @@ -1,77 +1,58 @@ -/* tclink.c - Library code for the TCLink client API. - * - * TCLink Copyright (c) 2013 TrustCommerce. - * http://www.trustcommerce.com - * techsupport@trustcommerce.com - * (949) 387-3747 - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ +/* tclink.c - Library code for the TCLink client API. */ #include "tclink.h" -#include -#include #include +#include +#include #include #ifdef WIN32 #include #include #else +#include +#include +#include +#include #include +#include #include #include -#include -#include -#include -#include #include #endif -#include -#include -#include #include #include - -#define OPENSSL_NO_KRB5 1 +#include +#include +#include #include -#include -#include -#include #include +#include #include +#include +#include #ifdef WIN32 -#define strcasecmp(x,y) stricmp(x,y) +#define strcasecmp(x, y) stricmp(x, y) #else -#define closesocket(x) close(x) +#define closesocket(x) close(x) #endif -#define DEFAULT_HOST "pgw1.trustcommerce.com" +#define DEFAULT_HOST "pgw1.trustcommerce.com" -/* changed from forty second to one hundred second to reflect more complicated transaction processing logic */ -#define TIMEOUT 100 /* seconds */ -#define TC_BUFF_MAX 16000 -#define TC_LINE_MAX ((PARAM_MAX_LEN * 2) + 2) +/* changed from forty second to one hundred second to reflect more complicated + * transaction processing logic */ +#define TIMEOUT 100 /* seconds */ +#define TC_BUFF_MAX 32000 +#define TC_LINE_MAX ((PARAM_MAX_LEN * 2) + 2) -char *tclink_version = TCLINK_VERSION; /* TCLINK_VERSION is defined in Makefile */ -char *tclink_host = DEFAULT_HOST; -int tclink_port = 443; +char tclink_version[] = + TCLINK_VERSION; /* TCLINK_VERSION is defined in Makefile */ +char tclink_host[] = DEFAULT_HOST; +int tclink_port = 443; /*************************************************/ /* Data structures used only within this module. */ @@ -81,37 +62,36 @@ int tclink_port = 443; typedef struct param_data { - char *name; - char *value; - struct param_data *next; + char* name; + char* value; + struct param_data* next; } param; typedef struct _TCLinkCon { - /* Connection data */ - int *ip; - int num_ips; - int sd; - - /* SSL encryption */ - const SSL_METHOD *meth; - long ctx_options; - SSL_CTX *ctx; - SSL *ssl; - - /* Transaction parameters, sent and received */ - param *send_param_list, *send_param_tail; - param *recv_param_list; - - /* Connection status */ - int is_error; - int pass; - time_t start_time; - int dns; - - char * trusted_ca_pem; - int (*validate_cert)(int, void *); - int full_ssl_close; + /* Connection data */ + int* ip; + int num_ips; + int sd; + + /* SSL encryption */ + const SSL_METHOD* meth; + long ctx_options; + SSL_CTX* ctx; + SSL* ssl; + + /* Transaction parameters, sent and received */ + param *send_param_list, *send_param_tail; + param* recv_param_list; + + /* Connection status */ + int is_error; + int pass; + time_t start_time; + int dns; + + int (*validate_cert)(int, void*); + int full_ssl_close; } TCLinkCon; @@ -120,898 +100,971 @@ typedef struct _TCLinkCon *************************************/ /* Random number from min to max. */ -static int number(int min, int max) +static int +number(int min, int max) { - return (rand() % (max - min + 1)) + min; + time_t t = time(0); + return (rand_r((unsigned int*)&t) % (max - min + 1)) + min; } /* Check if path points to a regular file */ -int is_regular_file(const char* path) +int +is_regular_file(const char* path) { - struct stat st; - stat(path, &st); - return S_ISREG(st.st_mode); + struct stat st; + stat(path, &st); + return S_ISREG(st.st_mode); } /* Safe string copy and append functions. */ -#define SAFE_COPY(d, s) safe_copy((d), (s), sizeof(d)); -#define SAFE_APPEND(d, s) safe_append((d), (s), sizeof(d)); +#define SAFE_COPY(d, s) safe_copy((d), (s), sizeof(d)); +#define SAFE_APPEND(d, s) safe_append((d), (s), sizeof(d)); -static void safe_copy(char *dst, const char *src, int size) +static bool +safe_copy(char* dst, const char* src, int size) { - int len = strlen(src); + int len = (int) strlen(src); if (len < size) strcpy(dst, src); else { strncpy(dst, src, size - 1); - dst[size-1] = 0; + dst[size - 1] = 0; } + return (len >= size); } -static void safe_append(char *dst, const char *src, int size) +static bool +safe_append(char* dst, const char* src, int size) { - int dlen = strlen(dst); - int slen = strlen(src); - int avail = size - dlen; - if (avail < 1) - return; + int dlen = (int) strlen(dst); + int slen = (int) strlen(src); + int avail = size - dlen; + if (avail < 1) + return true; if (slen < avail) - strcpy(dst+dlen, src); + strcpy(dst + dlen, src); else { - strncpy(dst+dlen, src, avail - 1); - dst[size-1] = 0; + strncpy(dst + dlen, src, avail - 1); + dst[size - 1] = 0; } + return (slen >= avail); } /* Add a parameter-value pair to the recieved list. */ -static void AddRecvParam(TCLinkCon *c, const char *name, const char *value) +static void +AddRecvParam(TCLinkCon* c, const char* name, const char* value) { - param *p; + param* p; - if (name[0] == 0 || value[0] == 0) - return; + if (name[0] == 0 || value[0] == 0) + return; - p = (param *)malloc(sizeof(param)); - p->name = strdup(name); - p->value = strdup(value); - p->next = c->recv_param_list; - c->recv_param_list = p; + p = (param*)malloc(sizeof(param)); + p->name = strdup(name); + p->value = strdup(value); + p->next = c->recv_param_list; + c->recv_param_list = p; } /* Add a string to the received list. */ -static int AddRecvString(TCLinkCon *c, char *string) +static int +AddRecvString(TCLinkCon* c, char* string) { - char *ptr = strchr(string, '='); - if (ptr == NULL) - return 0; + char* ptr = strchr(string, '='); + if (ptr == NULL) + return 0; - *ptr = 0; - AddRecvParam(c, string, ptr+1); + *ptr = 0; + AddRecvParam(c, string, ptr + 1); - return 1; + return 1; } /* Deallocate the send list. */ -static void ClearSendList(TCLinkCon *c) +static void +ClearSendList(TCLinkCon* c) { - param *p, *next; - for (p = c->send_param_list; p; p = next) - { - next = p->next; - free(p->name); - free(p->value); - free(p); - } - - c->send_param_list = c->send_param_tail = NULL; + param *p, *next; + for (p = c->send_param_list; p; p = next) { + next = p->next; + free(p->name); + free(p->value); + free(p); + } + + c->send_param_list = c->send_param_tail = NULL; } /* Deallocate the recv list. */ -static void ClearRecvList(TCLinkCon *c) +static void +ClearRecvList(TCLinkCon* c) { - param *p, *next; - for (p = c->recv_param_list; p; p = next) - { - next = p->next; - free(p->name); - free(p->value); - free(p); - } - - c->recv_param_list = NULL; + param *p, *next; + for (p = c->recv_param_list; p; p = next) { + next = p->next; + free(p->name); + free(p->value); + free(p); + } + + c->recv_param_list = NULL; +} + +void +do_SSL_randomize(void) +{ + enum + { + RAND_VALS = 32 + }; + int randbuf[RAND_VALS]; + char fname[512]; + int use_rand_file; + time_t t; + int i, c; + + /* if they have a /dev/urandom we can skip this function */ + if (RAND_status() != 0) + return; + + t = time(0); + RAND_seed((char*)&t, sizeof(time_t)); + + /* have they specified a random file with RANDFILE environment variable? */ + use_rand_file = RAND_file_name(fname, sizeof(fname)) ? 1 : 0; + if (use_rand_file) + RAND_load_file(fname, 4096); + + /* stuff it with packets of random numbers until it is satisfied */ + for (i = 0; i < 256 && RAND_status() == 0; i++) { + for (c = 0; c < RAND_VALS; c++) + randbuf[c] = rand_r((unsigned int*)&t); + RAND_seed((char*)randbuf, sizeof(int) * RAND_VALS); + } +} + +/* Make sure all of the ssl objects are properly initialized. + */ +static int +init_ssl(TCLinkCon* c) +{ + int ret = 1; + + /* Sanity Check */ + if (c == NULL) + return 0; + + /* do some SSL setup */ + if (!c->meth) { + do_SSL_randomize(); /* handle systems without /dev/urandom */ +#if OPENSSL_VERSION_NUMBER < 0x10100000L + c->meth = SSLv23_client_method(); + c->ctx_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1; +#else + c->meth = TLS_client_method(); + c->ctx_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | + SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TICKET; +#endif + } + + if (!c->ctx) { + int val; + int is_file; + + c->ctx = SSL_CTX_new(c->meth); + if (!c->ctx) + return 0; + /* set options */ + if (c->ctx_options) + SSL_CTX_set_options(c->ctx, c->ctx_options); +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + SSL_CTX_set_min_proto_version(c->ctx, TLS1_2_VERSION); +#endif + + is_file = is_regular_file(TCLINK_CA_PATH); + val = SSL_CTX_load_verify_locations( + c->ctx, is_file ? TCLINK_CA_PATH : NULL, is_file ? NULL : TCLINK_CA_PATH); + + if (!val) + return 0; // failed to populate cert store + + /* turn on certificate chain validation */ + SSL_CTX_set_verify(c->ctx, SSL_VERIFY_PEER, NULL); + } + + if (!c->ssl) { + c->ssl = SSL_new(c->ctx); + if (!c->ssl) { + SSL_CTX_free(c->ctx); + c->ctx = NULL; + return 0; + } + } + + return ret; } /* Open a socket to the host_ip specified. Returns the socket's file * descriptor on success (the open attempt is underway) or -1 for failure * (should never happen in practice). Note that this function DOES NOT block - * and wait for the connection; you'll need to select() on the socket later to see - * if it opened successfully. + * and wait for the connection; you'll need to select() on the socket later to + * see if it opened successfully. */ -static int BeginConnection(TCLinkCon *c, int host_ip) +static int +BeginConnection(TCLinkCon* c, int host_ip) { - struct sockaddr_in sa; - int sd; + struct sockaddr_in sa; + int sd; - sd = socket(AF_INET, SOCK_STREAM, 0); - if (sd < 0) - return -1; + sd = socket(AF_INET, SOCK_STREAM, 0); + if (sd < 0) + return -1; #ifdef WIN32 - u_long param = 1; - ioctlsocket(sd, FIONBIO, ¶m); + u_long param = 1; + ioctlsocket(sd, FIONBIO, ¶m); #else - fcntl(sd, F_SETFL, O_NONBLOCK); + fcntl(sd, F_SETFL, O_NONBLOCK); #endif - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_addr.s_addr = host_ip; - sa.sin_port = htons(tclink_port); + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_addr.s_addr = host_ip; + sa.sin_port = htons(tclink_port); - connect(sd, (struct sockaddr *) &sa, sizeof(sa)); + connect(sd, (struct sockaddr*)&sa, sizeof(sa)); - return sd; + return sd; } -/* This function is called on a socket file descriptor once the connection has been - * established and we're ready to negotiate SSL. If the SSL handshake fails for some - * reason (such as the host on the other end not using SSL), it will return 0 for - * failure. Success returns 1. +/* This function is called on a socket file descriptor once the connection has + * been established and we're ready to negotiate SSL. If the SSL handshake + * fails for some reason (such as the host on the other end not using SSL), it + * will return 0 for failure. Success returns 1. */ -static int FinishConnection(TCLinkCon *c, int sd) +static int +FinishConnection(TCLinkCon* c, int sd) { - int ssl_connected, is_error, errcode, res; - X509 *server_cert; - time_t start, remaining; - fd_set in, out, err; - struct timeval tv; - - /* check if socket has connected successfully */ - int val; - int /*socklen_t*/ size = 4; - getsockopt(sd, SOL_SOCKET, SO_ERROR, (char*)&val, &size); - if (val != 0) - return 0; - - SSL_clear(c->ssl); - - SSL_set_fd(c->ssl, sd); - - ssl_connected = 0; - is_error = 0; - start = time(0); - - while (!ssl_connected && !is_error) - { - - remaining = 5 - (time(0) - start); - if (remaining <= 0) { - is_error = 1; - break; - } - - res = SSL_connect(c->ssl); - - ssl_connected = ((res == 1) && SSL_is_init_finished(c->ssl)); - - if (!ssl_connected) - { - FD_ZERO(&in); FD_SET((unsigned)sd, &in); - FD_ZERO(&out); FD_SET((unsigned)sd, &out); - FD_ZERO(&err); FD_SET((unsigned)sd, &err); - /* the documentation does not suggest that both error types occur at the same time so - * the retry logic will consume all the outstanding events - * we do not actually use oob data, but if it is sent, it is treated as an error all the - * same - */ - errcode = SSL_get_error(c->ssl, res); - switch (errcode) - { - case SSL_ERROR_NONE: - /* no error, we should have a connection, check again */ - break; - - case SSL_ERROR_WANT_READ: - /* no error, just wait for more data */ - tv.tv_sec = remaining; tv.tv_usec = 0; - /* posix-2001 says the function will modify the appropriate descriptors */ - if (select(sd+1, &in, NULL, &err, &tv) < 0 || - FD_ISSET((unsigned)sd, &err) - ) - is_error = 1; - break; - case SSL_ERROR_WANT_WRITE: - /* no error, just wait for more data */ - tv.tv_sec = remaining; tv.tv_usec = 0; - if (select(sd+1, NULL, &out, &err, &tv) < 0 || - FD_ISSET((unsigned)sd, &err) - ) - is_error = 1; - break; - case SSL_ERROR_ZERO_RETURN: /* peer closed the connection */ - case SSL_ERROR_SSL: /* error in SSL handshake */ - default: - is_error = 1; - } - } - } - - if (is_error) { - return 0; - } - - + int ssl_connected, is_error, errcode, res; + X509* server_cert; + time_t start, remaining; + fd_set in, out, err; + struct timeval tv; + + /* check if socket has connected successfully */ + int val; + socklen_t size = 4; + getsockopt(sd, SOL_SOCKET, SO_ERROR, (char*)&val, &size); + if (val != 0) + return 0; + + if (c->ssl) + SSL_clear(c->ssl); + + ERR_clear_error(); + + // Call init_ssl to make sure everything is setup properly. + if (!init_ssl(c)) + return 0; + + SSL_set_fd(c->ssl, sd); + + ssl_connected = 0; + is_error = 0; + start = time(0); + + while (!ssl_connected && !is_error) { + + remaining = 5 - (time(0) - start); + if (remaining <= 0) { + is_error = 1; + break; + } + + res = SSL_connect(c->ssl); + + ssl_connected = ((res == 1) && SSL_is_init_finished(c->ssl)); + + if (!ssl_connected) { + FD_ZERO(&in); + FD_SET((unsigned)sd, &in); + FD_ZERO(&out); + FD_SET((unsigned)sd, &out); + FD_ZERO(&err); + FD_SET((unsigned)sd, &err); + /* the documentation does not suggest that both error types occur at the + * same time so the retry logic will consume all the outstanding events we + * do not actually use oob data, but if it is sent, it is treated as an + * error all the same + */ + errcode = SSL_get_error(c->ssl, res); + switch (errcode) { + case SSL_ERROR_NONE: + /* no error, we should have a connection, check again */ + break; + + case SSL_ERROR_WANT_READ: + /* no error, just wait for more data */ + tv.tv_sec = remaining; + tv.tv_usec = 0; + /* posix-2001 says the function will modify the appropriate + * descriptors */ + if (select(sd + 1, &in, NULL, &err, &tv) < 0 || + FD_ISSET((unsigned)sd, &err)) + is_error = 1; + break; + case SSL_ERROR_WANT_WRITE: + /* no error, just wait for more data */ + tv.tv_sec = remaining; + tv.tv_usec = 0; + if (select(sd + 1, NULL, &out, &err, &tv) < 0 || + FD_ISSET((unsigned)sd, &err)) + is_error = 1; + break; + case SSL_ERROR_ZERO_RETURN: /* peer closed the connection */ + case SSL_ERROR_SSL: /* error in SSL handshake */ + default: + is_error = 1; + } + } + } + + if (is_error) { + return 0; + } + #ifdef WIN32 - u_long param = 0; - ioctlsocket(sd, FIONBIO, ¶m); // make the socket blocking again + u_long param = 0; + ioctlsocket(sd, FIONBIO, ¶m); // make the socket blocking again #else - fcntl(sd, F_SETFL, 0); /* make the socket blocking again */ + fcntl(sd, F_SETFL, 0); /* make the socket blocking again */ #endif - - /* verify that server certificate is authentic */ - server_cert = SSL_get_peer_certificate(c->ssl); - if (!server_cert) { - return 0; - } - if (c->validate_cert && c->validate_cert(0, server_cert) != 0) - { - X509_free(server_cert); - return 0; - } - X509_free(server_cert); - - return 1; + + /* verify that server certificate is authentic */ + server_cert = SSL_get_peer_certificate(c->ssl); + if (!server_cert) { + return 0; + } + if (c->validate_cert && c->validate_cert(0, server_cert) != 0) { + X509_free(server_cert); + return 0; + } + X509_free(server_cert); + + return 1; } -/* This function should be called on list of socket file descriptors (sd) to determine - * if any have opened successfully. If so, it will return which one (index into - * the array). Otherwise it returns -1 if none have successfully opened. - * This function will block for a maximum of 3 seconds. - * As this function calls FinishConnection(), you shouldn't need to do anything special - * after it returns success - the socket is set up and ready for use. +/* This function should be called on list of socket file descriptors (sd) to + * determine if any have opened successfully. If so, it will return which one + * (index into the array). Otherwise it returns -1 if none have successfully + * opened. This function will block for a maximum of 3 seconds. As this function + * calls FinishConnection(), you shouldn't need to do anything special after it + * returns success - the socket is set up and ready for use. */ -static int CheckConnection(TCLinkCon *c, int *sd, int num_sd) +static int +CheckConnection(TCLinkCon* c, int* sd, int num_sd) { - fd_set wr_set, err_set; - struct timeval tv; - int max_sd = -1, i; - - tv.tv_sec = 3; /* wait 3 seconds for soc->mething to happen */ - tv.tv_usec = 0; - - /* build the fd_sets used for select() */ - FD_ZERO(&wr_set); - FD_ZERO(&err_set); - for (i = 0; i < num_sd; i++) - { - if (sd[i] < 0) continue; - FD_SET(sd[i], &wr_set); - FD_SET(sd[i], &err_set); - if (sd[i] > max_sd) - max_sd = sd[i]; - } - - /* run the select and see what we have waiting for us */ - if (select(max_sd + 1, NULL, &wr_set, &err_set, &tv) < 1) - return -1; /* I hope this never happens */ - - for (i = 0; i < num_sd; i++) - if (sd[i] >= 0) - { - if (FD_ISSET(sd[i], &err_set)) - { - /* error - close the socket and mark it defunct */ - close(sd[i]); - sd[i] = -1; - } - else if (FD_ISSET(sd[i], &wr_set)) - { - /* socket has opened! try to negotiate SSL */ - if (FinishConnection(c, sd[i])) { - /* socket is ready to go, so return success */ - c->sd = sd[i]; - return i; - } - else { - /* SSL handshake had errors, close the socket and mark it defunct */ - close(sd[i]); - sd[i] = -1; - } - } - } - - /* if we get here, nothing much interesting happened during those 3 seconds */ - return -1; -} + fd_set wr_set, err_set; + struct timeval tv; + int max_sd = -1, i; + + tv.tv_sec = 3; /* wait 3 seconds for soc->mething to happen */ + tv.tv_usec = 0; + + /* build the fd_sets used for select() */ + FD_ZERO(&wr_set); + FD_ZERO(&err_set); + for (i = 0; i < num_sd; i++) { + if (sd[i] < 0) + continue; + FD_SET(sd[i], &wr_set); + FD_SET(sd[i], &err_set); + if (sd[i] > max_sd) + max_sd = sd[i]; + } -void do_SSL_randomize() -{ - enum { RAND_VALS = 32 }; - int randbuf[RAND_VALS]; - char fname[512]; - int use_rand_file; - time_t t; - int i, c; - - /* if they have a /dev/urandom we can skip this function */ - if (RAND_status() != 0) - return; - - t = time(0); - RAND_seed((char *)&t, sizeof(time_t)); - - /* have they specified a random file with RANDFILE environment variable? */ - use_rand_file = RAND_file_name(fname, sizeof(fname)) ? 1 : 0; - if (use_rand_file) - RAND_load_file(fname, 4096); - - /* stuff it with packets of random numbers until it is satisfied */ - for (i = 0; i < 256 && RAND_status() == 0; i++) - { - for (c = 0; c < RAND_VALS; c++) - randbuf[c] = rand(); - RAND_seed((char *)randbuf, sizeof(int) * RAND_VALS); - } + /* run the select and see what we have waiting for us */ + if (select(max_sd + 1, NULL, &wr_set, &err_set, &tv) < 1) + return -1; /* I hope this never happens */ + + for (i = 0; i < num_sd; i++) + if (sd[i] >= 0) { + if (FD_ISSET(sd[i], &err_set)) { + /* error - close the socket and mark it defunct */ + close(sd[i]); + sd[i] = -1; + } else if (FD_ISSET(sd[i], &wr_set)) { + /* socket has opened! try to negotiate SSL */ + if (FinishConnection(c, sd[i])) { + /* socket is ready to go, so return success */ + c->sd = sd[i]; + return i; + } else { + /* SSL handshake had errors, close the socket and mark it defunct */ + close(sd[i]); + sd[i] = -1; + // Clear the ssl environment too. + if (c->ssl) { + SSL_free(c->ssl); + c->ssl = NULL; + } + } + } + } + + /* if we get here, nothing much interesting happened during those 3 seconds */ + return -1; } /* Open a connection to one of the TrustCommerce gateway servers. */ -static int Connect(TCLinkCon *c, int host_hash) +static int +Connect(TCLinkCon* c, int host_hash) { - struct hostent default_he; - char *addr_list[3]; int addr[2]; - struct hostent *he; - unsigned int **gw; - - enum { MAX_HOSTS = 32 }; - time_t last_connect[MAX_HOSTS]; - int sd[MAX_HOSTS]; - int num_sd = 0; - int host; - - int i, j, sort, sort_val; - - c->sd = -1; - c->is_error = 0; - - srand(time(0)); - - /* These are used as BACKUP ONLY if the DNS if offline. */ - addr[0] = inet_addr("207.38.46.42"); - addr[1] = inet_addr("208.42.227.151"); - addr_list[0] = (char *)&addr[0]; - addr_list[1] = (char *)&addr[1]; - addr_list[2] = 0; - default_he.h_addr_list = addr_list; - - /* determine IP addresses of gateway */ - if (!c->ip) - { - he = gethostbyname(tclink_host); - if (he) - c->dns = 1; - else { - /* fall back to hardcoded IPs in an emergency */ - c->dns = 0; - he = &default_he; - } - - for (c->num_ips = 0; he->h_addr_list[c->num_ips]; c->num_ips++) - ; - - c->ip = (int *)malloc(c->num_ips * sizeof(int)); - gw = (int unsigned **)he->h_addr_list; - - /* sort the IP address list before storing it */ - for (i = 0; i < c->num_ips; i++) - { - sort = 0; sort_val = *gw[0]; - for (j = 1; j < c->num_ips; j++) - if (*gw[j] > sort_val) - { - sort = j; - sort_val = *gw[j]; - } - - c->ip[i] = sort_val; - *gw[sort] = 0; - } - } - - /* do some SSL setup */ - if (!c->meth) - { - do_SSL_randomize(); /* handle systems without /dev/urandom */ - SSLeay_add_ssl_algorithms(); - c->meth = SSLv23_client_method(); - c->ctx_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; // Disable all known SSL versions - } - - if (!c->ctx) - { - int val; - - c->ctx = SSL_CTX_new(c->meth); - if (!c->ctx) return 0; - /* set options */ - if (c->ctx_options) - SSL_CTX_set_options(c->ctx, c->ctx_options); - - if (!c->trusted_ca_pem) - { - int is_file = is_regular_file(TCLINK_CA_PATH); - val = SSL_CTX_load_verify_locations(c->ctx, is_file?TCLINK_CA_PATH:NULL, is_file?NULL:TCLINK_CA_PATH); - } - else - { - extern int SSL_CTX_load_verify_locations_mem(SSL_CTX*, const char *); - val = SSL_CTX_load_verify_locations_mem(c->ctx, c->trusted_ca_pem); - } - - if (!val) return 0; // failed to populate cert store - - /* turn on certificate chain validation */ - SSL_CTX_set_verify(c->ctx, SSL_VERIFY_PEER, NULL); - } - - if (!c->ssl) - { - c->ssl = SSL_new(c->ctx); - if (!c->ssl) - { - SSL_CTX_free(c->ctx); - return 0; - } - } - - /* This loop works as follows: - * Grab the first host. Try to open a connection to it. If there was an - * error (host down or unreachable) go to the next one. If nothing has happened - * after 3 seconds, open a second socket (the first one is still open!) and try - * with the next fail-over host. Continue to do this for a maximum of MAX_HOSTS - * sockets, or until our TIMEOUT value runs out. We also keep track of how recently - * we tried to connect to a given host, so that we avoid saturating the machines - * in a heavy-load situation (which could be caused by anything from heavy internet - * lag between the local host and the TrustCommerce servers, to heavy load on the - * servers themselves due to half a million people trying to run credit card - * transactions in the same half second - unlikely, but certainly possible.) - */ - c->start_time = time(0); - c->pass = 1; - memset(last_connect, 0, MAX_HOSTS * sizeof(time_t)); - host = host_hash % c->num_ips; - - for ( ; time(0) < (c->start_time + TIMEOUT); c->pass++) - { - /* retry the first host at least once */ - if (c->pass > 2) host += 1; - if (host >= c->num_ips) host = 0; - - /* only connect if we haven't tried this host before, or it's been a little - * while (note random modifier to help stagger network traffic) */ - if (last_connect[host] == 0 || - (time(0) - last_connect[host]) >= number(TIMEOUT / 4, TIMEOUT)) - { - if (num_sd < MAX_HOSTS) - { - /* fire up a new connection to this host */ - if (c->pass != 1) - last_connect[host] = time(0); - - sd[num_sd] = BeginConnection(c, c->ip[host]); - if (sd[num_sd] >= 0) - num_sd++; - } - } - - /* scan all current sockets and see if we've made a successful connection - * somewhere. note that this also includes SSL and all that sort of fun, - * so once it returns success, we're all done. */ - if (num_sd > 0) - { - if (CheckConnection(c, sd, num_sd) >= 0) - { - /* Success: close all other file handles and return */ - for (i = 0; i < num_sd; i++) - if (sd[i] >= 0 && sd[i] != c->sd) - close(sd[i]); - - return 1; - } - } - - usleep(1000); // sleep for 1 millisecond - } - - return 0; + struct hostent default_he; + char* addr_list[3]; + int addr[2]; + unsigned int** gw = NULL; + + enum + { + MAX_HOSTS = 32 + }; + time_t last_connect[MAX_HOSTS]; + int sd[MAX_HOSTS]; + int num_sd = 0; + int host; + + int i, j, sort, sort_val; + + for (i = 0; i < MAX_HOSTS; i++) + sd[i] = -1; + + c->sd = -1; + c->is_error = 0; + + srand((unsigned) time(0)); + + /* These are used as BACKUP ONLY if the DNS if offline. */ + addr[0] = inet_addr("206.82.213.130"); + addr[1] = inet_addr("208.72.241.130"); + addr_list[0] = (char*)&addr[0]; + addr_list[1] = (char*)&addr[1]; + addr_list[2] = 0; + default_he.h_addr_list = addr_list; + + /* determine IP addresses of gateway */ + if (!c->ip) { +#ifndef __APPLE__ + int herr = 0; + char tmpbuf[4096]; + struct hostent tmpres; +#endif + struct hostent * he = NULL; + int ret = -1; + +#ifdef __APPLE__ + he = gethostbyname(tclink_host); + if (he) + ret = 0; +#else + ret = + gethostbyname_r(tclink_host, &tmpres, tmpbuf, sizeof(tmpbuf), &he, &herr); + if (ret) + he = NULL; + +#endif + if (ret == 0 && he != NULL) { + c->dns = 1; + } else { + /* fall back to hardcoded IPs in an emergency */ + c->dns = 0; + he = &default_he; + } + + for (c->num_ips = 0; he->h_addr_list[c->num_ips]; c->num_ips++) + ; + + c->ip = (int*)malloc(c->num_ips * sizeof(int)); + gw = (int unsigned**)he->h_addr_list; + + /* sort the IP address list before storing it */ + for (i = 0; i < c->num_ips; i++) { + sort = 0; + sort_val = *gw[0]; + for (j = 1; j < c->num_ips; j++) + if (*gw[j] > (unsigned int)sort_val) { + sort = j; + sort_val = *gw[j]; + } + + c->ip[i] = sort_val; + *gw[sort] = 0; + } + } + + if (!init_ssl(c)) + return 0; + + /* This loop works as follows: + * Grab the first host. Try to open a connection to it. If there was an + * error (host down or unreachable) go to the next one. If nothing has + * happened after 3 seconds, open a second socket (the first one is still + * open!) and try with the next fail-over host. Continue to do this for a + * maximum of MAX_HOSTS sockets, or until our TIMEOUT value runs out. We also + * keep track of how recently we tried to connect to a given host, so that we + * avoid saturating the machines in a heavy-load situation (which could be + * caused by anything from heavy internet lag between the local host and the + * TrustCommerce servers, to heavy load on the servers themselves due to half + * a million people trying to run credit card transactions in the same half + * second - unlikely, but certainly possible.) + */ + c->start_time = time(0); + c->pass = 1; + memset(last_connect, 0, MAX_HOSTS * sizeof(time_t)); + + host = host_hash % c->num_ips; + + for (; time(0) < (c->start_time + TIMEOUT); c->pass++) { + /* retry the first host at least once */ + if (c->pass > 2) + host += 1; + if (host >= c->num_ips) + host = 0; + + /* only connect if we haven't tried this host before, or it's been a little + * while (note random modifier to help stagger network traffic) */ + if (last_connect[host] == 0 || + (time(0) - last_connect[host]) >= number(TIMEOUT / 4, TIMEOUT)) { + if (num_sd < MAX_HOSTS) { + /* fire up a new connection to this host */ + if (c->pass != 1) + last_connect[host] = time(0); + + sd[num_sd] = BeginConnection(c, c->ip[host]); + if (sd[num_sd] >= 0) + num_sd++; + } + } + + /* scan all current sockets and see if we've made a successful connection + * somewhere. note that this also includes SSL and all that sort of fun, + * so once it returns success, we're all done. */ + if (num_sd > 0) { + if (CheckConnection(c, sd, num_sd) >= 0) { + /* Success: close all other file handles and return */ + for (i = 0; i < num_sd; i++) + if (sd[i] >= 0 && sd[i] != c->sd) + close(sd[i]); + + return 1; + } + } + + usleep(1000); // sleep for 1 millisecond + } + + // We couldn't connect successfully to any endpoint/s. + // Close any open sockets to avoid leaks. + for (i = 0; i < num_sd; i++) { + if (sd[i] >= 0) { + close(sd[i]); + sd[i] = -1; + } + } + + return 0; } -/* Send a chunk of data through a connection previously opened with Connect(). */ -static int Send(TCLinkCon *c, const char *string) +/* Send a chunk of data through a connection previously opened with Connect(). + */ +static int +Send(TCLinkCon* c, const char* string) { - if (SSL_write(c->ssl, string, strlen(string)) < 0) - return 0; + if (SSL_write(c->ssl, string, (unsigned) strlen(string)) < 0) + return 0; - return 1; + return 1; } -/* Peel a line off the current input. Note that this DOESN'T necessarily wait for all - * input to come in, only up to a "\n". -1 is returned for a network error, otherwise - * it returns the length of the line read. If there is not a complete line pending - * for read this will block until there is, or an error occurs. +/* Peel a line off the current input. Note that this DOESN'T necessarily wait + * for all input to come in, only up to a "\n". -1 is returned for a network + * error, otherwise it returns the length of the line read. If there is not a + * complete line pending for read this will block until there is, or an error + * occurs. */ -static int ReadLine(TCLinkCon *c, char *buffer, char *destbuf) +static int +ReadLine(TCLinkCon* c, char* buffer, char* destbuf) { - struct timeval tv; - fd_set read; - fd_set error; - int sel; - - while (1) /* we wait for a line to come in or an error to occur */ - { - char *eol = strchr(buffer, '\n'); - if (eol != NULL) - { - /* peel off the line and return it */ - *eol++ = 0; - safe_copy(destbuf, buffer, TC_LINE_MAX); - memmove(buffer, eol, strlen(eol)+1); - return strlen(destbuf); - } - else - { - if (c->is_error == 1) - return -1; - - /* do socket work to grab the most recent chunk of incoming data */ - FD_ZERO(&read); FD_SET(c->sd, &read); - FD_ZERO(&error); FD_SET(c->sd, &error); - tv.tv_sec = TIMEOUT; - tv.tv_usec = 0; - - sel = select(c->sd+1, &read, NULL, &error, &tv); - if (sel < 1) - c->is_error = 1; - else if (FD_ISSET(c->sd, &error)) - c->is_error = 1; - else if (FD_ISSET(c->sd, &read)) - { - int buffer_end = strlen(buffer); - int size = SSL_read(c->ssl, buffer + buffer_end, TC_BUFF_MAX-1 - buffer_end); - if (size == 0) - { - int error_type = SSL_get_error(c->ssl, size); - switch (error_type) - { - /* this would never happen in practice */ - case SSL_ERROR_NONE: - /* this wouldn't happen either because the ssl transport is blocking */ - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - buffer[buffer_end] = 0; - break; - - /* these others should not really happen but if they do, we bail */ - /* we would never get any more data and it looks like the callee is expecting something */ - case SSL_ERROR_ZERO_RETURN: - case SSL_ERROR_WANT_CONNECT: - case SSL_ERROR_WANT_ACCEPT: - case SSL_ERROR_SYSCALL: - case SSL_ERROR_WANT_X509_LOOKUP: - case SSL_ERROR_SSL: - default: - c->is_error = 1; - break; - } - } - else if (size < 0) - c->is_error = 1; - else - buffer[buffer_end + size] = 0; - } - } - } + struct timeval tv; + fd_set read; + fd_set error; + int sel; + + while (1) /* we wait for a line to come in or an error to occur */ + { + char* eol = strchr(buffer, '\n'); + if (eol != NULL) { + /* peel off the line and return it */ + *eol++ = 0; + safe_copy(destbuf, buffer, TC_LINE_MAX); + memmove(buffer, eol, strlen(eol) + 1); + return (int) strlen(destbuf); + } else { + if (c->is_error == 1) + return -1; + + /* do socket work to grab the most recent chunk of incoming data */ + FD_ZERO(&read); + FD_SET(c->sd, &read); + FD_ZERO(&error); + FD_SET(c->sd, &error); + tv.tv_sec = TIMEOUT; + tv.tv_usec = 0; + + sel = select(c->sd + 1, &read, NULL, &error, &tv); + if (sel < 1) + c->is_error = 1; + else if (FD_ISSET(c->sd, &error)) + c->is_error = 1; + else if (FD_ISSET(c->sd, &read)) { + int buffer_end = (int) strlen(buffer); + int size = + SSL_read(c->ssl, buffer + buffer_end, TC_BUFF_MAX - 1 - buffer_end); + if (size == 0) { + int error_type = SSL_get_error(c->ssl, size); + switch (error_type) { + /* this would never happen in practice */ + case SSL_ERROR_NONE: + /* this wouldn't happen either because the ssl transport is blocking + */ + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + buffer[buffer_end] = 0; + break; + + /* these others should not really happen but if they do, we bail */ + /* we would never get any more data and it looks like the callee is + * expecting something */ + case SSL_ERROR_ZERO_RETURN: + case SSL_ERROR_WANT_CONNECT: + case SSL_ERROR_WANT_ACCEPT: + case SSL_ERROR_SYSCALL: + case SSL_ERROR_WANT_X509_LOOKUP: + case SSL_ERROR_SSL: + default: + c->is_error = 1; + break; + } + } else if (size < 0) + c->is_error = 1; + else + buffer[buffer_end + size] = 0; + } + } + } } -/* Closes a connection opened with Connect() and frees memory associated with it. - * You ONLY need to Close() connections which opened successfully; those that don't - * clean up after themselves before Connect() returns. +/* Closes a connection opened with Connect() and frees memory associated with + * it. You ONLY need to Close() connections which opened successfully; those + * that don't clean up after themselves before Connect() returns. */ -static int Close(TCLinkCon *c) +static int +Close(TCLinkCon* c) { - if (c->ssl) - { - /* The full shutdown presented here is more for completeness than necessity; at this point in the - * application, we have already received the end trailer (or bust) which is generally accompanied by - * a close notify message. If the software chooses to respond to the close notify (per TLS specification) - * this would result in at least reading the incoming close notify and issuing our own. Because this entails - * an additional round trip that is not needed (the transaction is done after the accompanying END), there - * does not appear to be a benefit to it at all. By default though, this configuration is enabled and - * can be disabled by the integrator for performance reasons. - */ - if (c->full_ssl_close) - { - int status = SSL_shutdown(c->ssl); - if (status == 0) status = SSL_shutdown(c->ssl); - } - else - SSL_set_shutdown(c->ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN); - } - - if (c->sd >= 0) { - close(c->sd); - c->sd = -1; - } - - if (c->trusted_ca_pem) { - free(c->trusted_ca_pem); - c->trusted_ca_pem = NULL; - } - - return 1; + if (c->ssl) { + /* The full shutdown presented here is more for completeness than necessity; + * at this point in the application, we have already received the end + * trailer (or bust) which is generally accompanied by a close notify + * message. If the software chooses to respond to the close notify (per TLS + * specification) this would result in at least reading the incoming close + * notify and issuing our own. Because this entails an additional round + * trip that is not needed (the transaction is done after the accompanying + * END), there does not appear to be a benefit to it at all. By default + * though, this configuration is enabled and can be disabled by the + * integrator for performance reasons. + */ + if (c->full_ssl_close) { + int status = SSL_shutdown(c->ssl); + if (status == 0) + status = SSL_shutdown(c->ssl); + } else + SSL_set_shutdown(c->ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN); + } + + if (c->sd >= 0) { + close(c->sd); + c->sd = -1; + } + + return 1; } -static void stuff_string(char *buf, int *len, int size, const char *add) +static void +stuff_string(char* buf, int* len, int size, const char* add) { - int newlen = strlen(add); - if ((*len + newlen) >= size) - newlen = size - *len - 1; - if (newlen < 1) return; - strncpy(buf + *len, add, newlen); - *len += newlen; - buf[*len] = 0; + int newlen = (int) strlen(add); + if ((*len + newlen) >= size) + newlen = size - *len - 1; + if (newlen < 1) + return; + strncpy(buf + *len, add, newlen); + *len += newlen; + buf[*len] = 0; } /********************************************** * API functions exported to the user client. * **********************************************/ -TCLinkHandle TCLinkCreate() +TCLinkHandle +TCLinkCreate() { - extern int TCLinkDefaultValidate(int, void *); + extern int TCLinkDefaultValidate(int, void*); - TCLinkCon *c = (TCLinkCon *)malloc(sizeof(TCLinkCon)); + TCLinkCon* c = (TCLinkCon*)malloc(sizeof(TCLinkCon)); - c->ip = NULL; - c->num_ips = 0; - c->sd = -1; + c->ip = NULL; + c->num_ips = 0; + c->sd = -1; - c->meth = NULL; - c->ctx = NULL; - c->ssl = NULL; + c->meth = NULL; + c->ctx_options = 0; + c->ctx = NULL; + c->ssl = NULL; - c->send_param_list = NULL; - c->send_param_tail = NULL; - c->recv_param_list = NULL; + c->send_param_list = NULL; + c->send_param_tail = NULL; + c->recv_param_list = NULL; - c->is_error = 0; - c->pass = 0; - c->start_time = 0; - c->dns = -1; + c->is_error = 0; + c->pass = 0; + c->start_time = 0; + c->dns = -1; - c->trusted_ca_pem = NULL; - c->validate_cert = TCLinkDefaultValidate; - c->full_ssl_close = 1; + c->validate_cert = TCLinkDefaultValidate; + c->full_ssl_close = 1; - return (TCLinkHandle)c; + return (TCLinkHandle)c; } -int TCLinkSetFullClose(TCLinkHandle handle, int full_ssl_close) +int +TCLinkSetFullClose(TCLinkHandle handle, int full_ssl_close) { - TCLinkCon *c = (TCLinkCon *)handle; - int swap = c->full_ssl_close; - c->full_ssl_close = full_ssl_close ? 1 : 0; - return swap; -} - -void TCLinkSetTrustedCABundle(TCLinkHandle handle, const char *str, int len) -{ - TCLinkCon *c = (TCLinkCon *)handle; - - if (c->trusted_ca_pem) - free(c->trusted_ca_pem); - - if (str == NULL) - { - c->trusted_ca_pem = NULL; - return; - } - - c->trusted_ca_pem = malloc(len+1); - strncpy(c->trusted_ca_pem,str,len); - c->trusted_ca_pem[len] = 0; + TCLinkCon* c = (TCLinkCon*)handle; + int swap = c->full_ssl_close; + c->full_ssl_close = full_ssl_close ? 1 : 0; + return swap; } -void TCLinkSetValidateCallback(TCLinkHandle handle, int (*validate_cert)(int, void *)) +void +TCLinkSetValidateCallback(TCLinkHandle handle, int (*validate_cert)(int, void*)) { - TCLinkCon *c = (TCLinkCon *)handle; - if (validate_cert == NULL) - { - extern int TCLinkDefaultValidate(int, void *); - c->validate_cert = TCLinkDefaultValidate; - } - else - c->validate_cert = validate_cert; + TCLinkCon* c = (TCLinkCon*)handle; + if (validate_cert == NULL) { + extern int TCLinkDefaultValidate(int, void*); + c->validate_cert = TCLinkDefaultValidate; + } else + c->validate_cert = validate_cert; } - -void TCLinkPushParam(TCLinkHandle handle, const char *name, const char *value) + +void +TCLinkPushParam(TCLinkHandle handle, const char* name, const char* value) { - param *p; - char *ch; - - TCLinkCon *c = (TCLinkCon *)handle; - - if (name && value) - { - p = (param *)malloc(sizeof(param)); - p->name = strdup(name); - p->value = strdup(value); - p->next = NULL; - if (c->send_param_tail) - c->send_param_tail->next = p; - else - c->send_param_list = p; - c->send_param_tail = p; - - /* remove newlines and equals signs from the parameter name */ - for (ch = p->name; *ch; ch++) - if (*ch == '=' || *ch == '\n') *ch = ' '; - - /* remove newlines from the value */ - for (ch = p->value; *ch; ch++) - if (*ch == '\n') *ch = ' '; - } + param* p; + char* ch; + + TCLinkCon* c = (TCLinkCon*)handle; + + if (name && value) { + p = (param*)malloc(sizeof(param)); + p->name = strdup(name); + p->value = strdup(value); + p->next = NULL; + if (c->send_param_tail) + c->send_param_tail->next = p; + else + c->send_param_list = p; + c->send_param_tail = p; + + /* remove newlines and equals signs from the parameter name */ + for (ch = p->name; *ch; ch++) + if (*ch == '=' || *ch == '\n') + *ch = ' '; + + /* remove newlines from the value */ + for (ch = p->value; *ch; ch++) + if (*ch == '\n') + *ch = ' '; + } } -void TCLinkSend(TCLinkHandle handle) +void +TCLinkSend(TCLinkHandle handle) { - param *p, *next; - char buf[TC_BUFF_MAX], destbuf[TC_LINE_MAX]; - char buf2[1024]; - int host_hash = 1; - int retval = 0; - - TCLinkCon *c = (TCLinkCon *)handle; - - ClearRecvList(c); - - /* build most of the string we will send to the processor */ - sprintf(buf, "BEGIN\nversion=%s\n", tclink_version); - - for (p = c->send_param_list; p; p = next) - { - next = p->next; - SAFE_COPY(buf2, p->name); - SAFE_APPEND(buf2, "="); - SAFE_APPEND(buf2, p->value); - SAFE_APPEND(buf2, "\n"); - SAFE_APPEND(buf, buf2); - if (!strcasecmp(p->name, "custid")) { - host_hash = atoi(p->value); - host_hash = (host_hash / 100) + (host_hash % 100); - } - free(p->name); - free(p->value); - free(p); - } - - c->send_param_list = c->send_param_tail = NULL; - - /* try to make the connection */ - if (!Connect(c, host_hash)) - { - Close(c); /* clean up any memory Connect() may have left lying around */ - AddRecvParam(c, "status", "error"); - AddRecvParam(c, "errortype", "cantconnect"); - return; - } - - /* append some data about the connection */ - sprintf(buf+strlen(buf), "pass=%d\ntime=%ld\n", c->pass, time(0) - c->start_time); - if (c->dns != 1) SAFE_APPEND(buf, "dns=n\n"); - SAFE_APPEND(buf, "END\n"); - - /* send the data */ - if (Send(c, buf)) - { - int state = 0; - buf[0] = destbuf[0] = 0; /* recycle buf */ - c->is_error = 0; - while (1) - { - int len = ReadLine(c, buf, destbuf); - if (len == 0) continue; - if (len < 0) break; - if (strcasecmp(destbuf, "BEGIN") == 0) - { - if (state != 0) - { state = -1; break; } - state = 1; - } - else if (strcasecmp(destbuf, "END") == 0) - { - state = (state != 1) ? -1 : 2; - break; - } - else - { - if (state != 1 || !AddRecvString(c, destbuf)) - { state = -1; break; } - } - } - if (state == 2) - retval = 1; - } - - Close(c); - - if (!retval) - { - ClearRecvList(c); - AddRecvParam(c, "status", "error"); - AddRecvParam(c, "errortype", "linkfailure"); - } + param *p, *next; + char buf[TC_BUFF_MAX], destbuf[TC_LINE_MAX]; + const int BUF2_LEN = 2048; + char buf2[BUF2_LEN]; + int host_hash = 1; + int retval = 0; + bool full = false; + + TCLinkCon* c = (TCLinkCon*)handle; + + ClearRecvList(c); + + /* build most of the string we will send to the processor */ + sprintf(buf, "BEGIN\nversion=%s\n", tclink_version); + + for (p = c->send_param_list; p; p = next) { + next = p->next; + full = full || SAFE_COPY(buf2, p->name); + full = full || SAFE_APPEND(buf2, "="); + full = full || SAFE_APPEND(buf2, p->value); + full = full || SAFE_APPEND(buf2, "\n"); + full = full || SAFE_APPEND(buf, buf2); + if (!full && !strcasecmp(p->name, "custid")) { + + host_hash = atoi(p->value); + host_hash = (host_hash / 100) + (host_hash % 100); + } + + free(p->name); + free(p->value); + free(p); + } + + c->send_param_list = c->send_param_tail = NULL; + + /* try to make the connection */ + if (!full && !Connect(c, host_hash)) { + Close(c); /* clean up any memory Connect() may have left lying around */ + AddRecvParam(c, "status", "error"); + AddRecvParam(c, "errortype", "cantconnect"); + return; + } + + if (!full) { + /* append some data about the connection */ + snprintf( + buf2, BUF2_LEN, "pass=%d\ntime=%ld\n", c->pass, time(0) - c->start_time); + full = full || SAFE_APPEND(buf, buf2); + if (c->dns != 1) + SAFE_APPEND(buf, "dns=n\n"); + full = full || SAFE_APPEND(buf, "END\n"); + } + + if (full) { + Close(c); + AddRecvParam(c, "status", "baddata"); + AddRecvParam(c, "error", "badlength"); + AddRecvParam(c, "offenders", "request"); + return; + } + + /* send the data */ + if (Send(c, buf)) { + int state = 0; + buf[0] = destbuf[0] = 0; /* recycle buf */ + c->is_error = 0; + while (1) { + int len = ReadLine(c, buf, destbuf); + if (len == 0) + continue; + if (len < 0) + break; + if (strcasecmp(destbuf, "BEGIN") == 0) { + if (state != 0) { + state = -1; + break; + } + state = 1; + } else if (strcasecmp(destbuf, "END") == 0) { + state = (state != 1) ? -1 : 2; + break; + } else { + if (state != 1 || !AddRecvString(c, destbuf)) { + state = -1; + break; + } + } + } + if (state == 2) + retval = 1; + } + + Close(c); + + if (!retval) { + ClearRecvList(c); + AddRecvParam(c, "status", "error"); + AddRecvParam(c, "errortype", "linkfailure"); + } } - -char *TCLinkGetResponse(TCLinkHandle handle, const char *name, char *value) + +char* +TCLinkGetResponse(TCLinkHandle handle, const char* name, char* value) { - param *p; - TCLinkCon *c = (TCLinkCon *)handle; + param* p; + TCLinkCon* c = (TCLinkCon*)handle; - for (p = c->recv_param_list; p; p = p->next) - if (strcasecmp(name, p->name) == 0) - { - safe_copy(value, p->value, PARAM_MAX_LEN); - return value; - } + for (p = c->recv_param_list; p; p = p->next) + if (strcasecmp(name, p->name) == 0) { + safe_copy(value, p->value, PARAM_MAX_LEN); + return value; + } - return NULL; + return NULL; } -char *TCLinkGetEntireResponse(TCLinkHandle handle, char *buf, int size) +char* +TCLinkGetEntireResponse(TCLinkHandle handle, char* buf, int size) { - param *p; - int len = 0; - TCLinkCon *c = (TCLinkCon *)handle; - - for (p = c->recv_param_list; p; p = p->next) { - stuff_string(buf, &len, size, p->name); - stuff_string(buf, &len, size, "="); - stuff_string(buf, &len, size, p->value); - stuff_string(buf, &len, size, "\n"); - } - - return buf; + param* p; + int len = 0; + TCLinkCon* c = (TCLinkCon*)handle; + + for (p = c->recv_param_list; p; p = p->next) { + stuff_string(buf, &len, size, p->name); + stuff_string(buf, &len, size, "="); + stuff_string(buf, &len, size, p->value); + stuff_string(buf, &len, size, "\n"); + } + + return buf; } -void TCLinkDestroy(TCLinkHandle handle) +void +TCLinkDestroy(TCLinkHandle handle) { - TCLinkCon *c = (TCLinkCon *)handle; - if (!c) return; + TCLinkCon* c = (TCLinkCon*)handle; + if (!c) + return; - ClearSendList(c); - ClearRecvList(c); - Close(c); + ClearSendList(c); + ClearRecvList(c); + Close(c); - if (c->ip) - free(c->ip); + if (c->ip) + free(c->ip); - if (c->ssl) { - SSL_free(c->ssl); - c->ssl = NULL; - } + if (c->ssl) { + SSL_free(c->ssl); + c->ssl = NULL; + } - if (c->ctx) { - SSL_CTX_free(c->ctx); - c->ctx = NULL; - } + if (c->ctx) { + SSL_CTX_free(c->ctx); + c->ctx = NULL; + } - free(c); + free(c); } -char *TCLinkGetVersion(char *buf) +char* +TCLinkGetVersion(char* buf) { - strcpy(buf, tclink_version); - return buf; + strcpy(buf, tclink_version); + return buf; } - diff --git a/tclink.h b/tclink.h index 83862ef..6623ffc 100644 --- a/tclink.h +++ b/tclink.h @@ -1,24 +1,4 @@ -/* tclink.h - Header file for TCLink library. - * - * TCLink Copyright (c) 2013 TrustCommerce. - * http://www.trustcommerce.com - * techsupport@trustcommerce.com - * (949) 387-3747 - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ +/* tclink.h - Header file for TCLink library. */ #ifndef _TCLINK_H #define _TCLINK_H @@ -29,55 +9,64 @@ * for each concurrent thread, but the same handle can be shared by transactions * occurring one after another (such as a for loop). */ -#define TCLinkHandle void * +#define TCLinkHandle void* /* Parameter names and values cannot exceed this size. */ -#define PARAM_MAX_LEN 256 +#define PARAM_MAX_LEN 768 /* Create a new TCLinkHandle. */ -TCLinkHandle TCLinkCreate(); +TCLinkHandle +TCLinkCreate(); /* Add a parameter to be sent to the server. */ -void TCLinkPushParam(TCLinkHandle handle, const char *name, const char *value); +void +TCLinkPushParam(TCLinkHandle handle, const char* name, const char* value); /* Flush the parameters to the server. */ -void TCLinkSend(TCLinkHandle handle); +void +TCLinkSend(TCLinkHandle handle); /* Look up a response value from the server. * Returns NULL if no such parameter, or stores the value in 'value' and * returns a pointer to value. value should be at least PARAM_MAX_LEN in size. */ -char *TCLinkGetResponse(TCLinkHandle handle, const char *name, char *value); +char* +TCLinkGetResponse(TCLinkHandle handle, const char* name, char* value); /* Get all response values from the server in one giant string. * Stores the string into buf and returns a pointer to it. Size should be * sizeof(buf), which will limit the string so that no buffer overruns occur. */ -char *TCLinkGetEntireResponse(TCLinkHandle handle, char *buf, int size); +char* +TCLinkGetEntireResponse(TCLinkHandle handle, char* buf, int size); -/* Destory a handle, ending that transaction and freeing the memory associated with it. */ -void TCLinkDestroy(TCLinkHandle handle); +/* Destory a handle, ending that transaction and freeing the memory associated + * with it. */ +void +TCLinkDestroy(TCLinkHandle handle); /* Store version string into buf. Returns a pointer to buf. */ -char *TCLinkGetVersion(char *buf); - +char* +TCLinkGetVersion(char* buf); /* The API methods below are subject to change. */ -/* Enables (1) or Disables (0) the full SSL close_notify sequence. By default, this is set to 1.*/ -int TCLinkSetFullClose(TCLinkHandle handle, int full_ssl_close); +/* Enables (1) or Disables (0) the full SSL close_notify sequence. By default, + * this is set to 1.*/ +int +TCLinkSetFullClose(TCLinkHandle handle, int full_ssl_close); -/* Provides a method, before the first send call is initiated, of loading a set of trusted CA certificates (PEM format). */ -void TCLinkSetTrustedCABundle(TCLinkHandle handle, const char *str, int len); - -/* Provides a method, once the handshake is completed, a means to verify the contents of that certificate independently. - * Note that the certificate may not be set depending on the negotation type (in which case the pointer would be NULL) +/* Provides a method, once the handshake is completed, a means to verify the + * contents of that certificate independently. Note that the certificate may not + * be set depending on the negotation type (in which case the pointer would be + * NULL) */ -void TCLinkSetValidateCallback(TCLinkHandle handle, int (*validate_cert)(int, void *)); +void +TCLinkSetValidateCallback(TCLinkHandle handle, + int (*validate_cert)(int, void*)); #endif - diff --git a/tctest.php b/tctest.php index 1edab30..54ba754 100644 --- a/tctest.php +++ b/tctest.php @@ -15,10 +15,10 @@ // Build a hash containing our parameters $params['custid'] = 'XXXX'; $params['password'] = 'PPPP'; - $params['action'] = 'sale'; + $params['action'] = 'preauth'; $params['media'] = 'cc'; $params['cc'] = '4111111111111111'; - $params['exp'] = '0110'; + $params['exp'] = '0429'; $params['amount'] = '100'; $params['name'] = 'Joe PHP'; @@ -31,4 +31,3 @@ while (list($key, $val) = each($result)) print "\t$key=$val\n"; ?> - diff --git a/validate.c b/validate.c index 3c7dcc5..83d6b8d 100644 --- a/validate.c +++ b/validate.c @@ -1,14 +1,14 @@ /* COPYRIGHT AND PERMISSION NOTICE - + Copyright (c) 1996 - 2010, Daniel Stenberg, . - + All rights reserved. - + Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN @@ -16,139 +16,150 @@ NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - + Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization of the copyright holder. */ /* simplified to a basic host name check */ -#include #include #include +#include #define bool int #define false 0 #define true 1 /** @fn static bool cert_hostcheck(const char *hostname, char *pattern) - * Verifies that the hostname matches against the pattern specified. - * Handles wildcard patterns and ignores the distinction between upper and lower case letters. - * Note: Ported over from ssluse.c in curl (7.1.16) lib - * Note: Explicit pattern match disabled as we do not use that for processing node certificate. - * Note: No longer ignores the distinction between upper and lower case letters. Our certificate is generated with lowercase letters. - * @return true if matches, false otherwise - * @param hostname The hostname we want to check. e.g: vault.trustcommerce.com - * @param pattern The pattern we wish to match against. e.g: *.trustcommerce.com - */ -bool cert_hostcheck(const char *pattern, const char *hostname) + * Verifies that the hostname matches against the pattern specified. + * Handles wildcard patterns and ignores the distinction between upper and lower + * case letters. Note: Ported over from ssluse.c in curl (7.1.16) lib Note: + * Explicit pattern match disabled as we do not use that for processing node + * certificate. Note: No longer ignores the distinction between upper and lower + * case letters. Our certificate is generated with lowercase letters. + * @return true if matches, false otherwise + * @param hostname The hostname we want to check. e.g: vault.trustcommerce.com + * @param pattern The pattern we wish to match against. e.g: *.trustcommerce.com + */ +bool +cert_hostcheck(const char* pattern, const char* hostname) { - if (!hostname || !pattern || !*hostname || !*pattern) return false; - if (!strcmp(hostname,pattern)) return true; - return false; + if (!hostname || !pattern || !*hostname || !*pattern) + return false; + if (!strcmp(hostname, pattern)) + return true; + return false; } /** @fn static bool checkCertificate(X509 *cert, char *host) - * Provides validation of the hostname associated with a certificate. - * See RFC2818 - Server Identity for an overview of the concept. - * This implementation is based off the one found in curl-7.16.1: ssluse.c - * but we treat the subjectAltName as a recommendation... so if it fails, - * we will proceed to the CN check. - * The rationale for this is that we are not always using HTTP (over SSL) - * and its more of a certification generation / CA issue and we want - * maximum interoperability (as opposed to strict compliance). - * @param cert The X509 certificate in question. - * @param host The hostname or ip we wish to check. - * @return true if matches, false otherwise - */ -static bool checkCertificate(X509 * cert, const char *host) + * Provides validation of the hostname associated with a certificate. + * See RFC2818 - Server Identity for an overview of the concept. + * This implementation is based off the one found in curl-7.16.1: ssluse.c + * but we treat the subjectAltName as a recommendation... so if it fails, + * we will proceed to the CN check. + * The rationale for this is that we are not always using HTTP (over SSL) + * and its more of a certification generation / CA issue and we want + * maximum interoperability (as opposed to strict compliance). + * @param cert The X509 certificate in question. + * @param host The hostname or ip we wish to check. + * @return true if matches, false otherwise + */ +static bool +checkCertificate(X509* cert, const char* host) { - int i,j; - bool matched = false; - STACK_OF(GENERAL_NAME) * altnames; - unsigned char *nulstr = { '\0' }; - unsigned char *peer_CN = nulstr; - X509_NAME *name; - ASN1_STRING * tmp; - bool status = false; - - if (!cert || !host) return false; - - altnames = (STACK_OF(GENERAL_NAME) *)(X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL)); - - if (altnames != NULL) - { - int numalts = sk_GENERAL_NAME_num(altnames); - for (i=0; (id.ia5)); - size_t altlen; - switch (check->type) - { - case GEN_DNS: - altlen = ASN1_STRING_length(check->d.ia5); - if (altlen == strlen(host) && cert_hostcheck(altptr, host)) - matched = true; - break; - case GEN_IPADD: - altlen = ASN1_STRING_length(check->d.ia5); - if (altlen == strlen(host) && !memcmp(altptr, host, altlen)) - matched = true; - break; - } - } - GENERAL_NAMES_free(altnames); - if (matched != false) return true; - } - - i = j = -1; - - - name = X509_get_subject_name(cert); - if (!name) return false; - - - // get the last CN found in the subject (supposedly its the most distinguished one) - while ((j=X509_NAME_get_index_by_NID(name,NID_commonName,i))>=0) - i=j; - - if (i<0) return false; - - tmp = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, i)); - /* workaround for version of openssl < 0.9.7d */ - if (tmp && ASN1_STRING_type(tmp) == V_ASN1_UTF8STRING) - { - j = ASN1_STRING_length(tmp); - if (j >= 0) { - peer_CN = (unsigned char *)(OPENSSL_malloc(j+1)); - if (peer_CN) - { - memcpy(peer_CN, ASN1_STRING_data(tmp), j); - peer_CN[j] = '\0'; - } - } - } - else - { - j = ASN1_STRING_to_UTF8(&peer_CN, tmp); - } - - if (peer_CN == nulstr) - peer_CN = NULL; - - if (peer_CN == NULL) - return false; // the cn isnt missing in virtually all cases - else if(!cert_hostcheck((char *)(peer_CN), host)) - status = false; - else - status = true; - - if (peer_CN) - OPENSSL_free(peer_CN); - return status; + int i, j; + bool matched = false; + STACK_OF(GENERAL_NAME) * altnames; + unsigned char nulstr[] = { '\0' }; + unsigned char* peer_CN = nulstr; + X509_NAME* name; + ASN1_STRING* tmp; + bool status = false; + + if (!cert || !host) + return false; + + altnames = (STACK_OF(GENERAL_NAME)*)(X509_get_ext_d2i( + cert, NID_subject_alt_name, NULL, NULL)); + + if (altnames != NULL) { + int numalts = sk_GENERAL_NAME_num(altnames); + for (i = 0; (i < numalts) && (matched == false); i++) { + const GENERAL_NAME* check = sk_GENERAL_NAME_value(altnames, i); +#if OPENSSL_VERSION_NUMBER < 0x10100000L + const char* altptr = (char*)(ASN1_STRING_data(check->d.ia5)); +#else + const char* altptr = (char*)(ASN1_STRING_get0_data(check->d.ia5)); +#endif + size_t altlen; + switch (check->type) { + case GEN_DNS: + altlen = ASN1_STRING_length(check->d.ia5); + if (altlen == strlen(host) && cert_hostcheck(altptr, host)) + matched = true; + break; + case GEN_IPADD: + altlen = ASN1_STRING_length(check->d.ia5); + if (altlen == strlen(host) && !memcmp(altptr, host, altlen)) + matched = true; + break; + } + } + GENERAL_NAMES_free(altnames); + if (matched != false) + return true; + } + + i = j = -1; + + name = X509_get_subject_name(cert); + if (!name) + return false; + + // get the last CN found in the subject (supposedly its the most distinguished + // one) + while ((j = X509_NAME_get_index_by_NID(name, NID_commonName, i)) >= 0) + i = j; + + if (i < 0) + return false; + + tmp = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, i)); + /* workaround for version of openssl < 0.9.7d */ + if (tmp && ASN1_STRING_type(tmp) == V_ASN1_UTF8STRING) { + j = ASN1_STRING_length(tmp); + if (j >= 0) { + peer_CN = (unsigned char*)(OPENSSL_malloc(j + 1)); + if (peer_CN) { +#if OPENSSL_VERSION_NUMBER < 0x10100000L + memcpy(peer_CN, ASN1_STRING_data(tmp), j); +#else + memcpy(peer_CN, ASN1_STRING_get0_data(tmp), j); +#endif + peer_CN[j] = '\0'; + } + } + } else { + j = ASN1_STRING_to_UTF8(&peer_CN, tmp); + } + + if (peer_CN == nulstr) + peer_CN = NULL; + + if (peer_CN == NULL) + return false; // the cn isnt missing in virtually all cases + else if (!cert_hostcheck((char*)(peer_CN), host)) + status = false; + else + status = true; + + if (peer_CN) + OPENSSL_free(peer_CN); + return status; } -int TCLinkDefaultValidate(int x, void * cert) +int +TCLinkDefaultValidate(int x, void* cert) { - if (x != 0 || cert == NULL) return 0; - return !checkCertificate((X509 *)cert, "pgw1.trustcommerce.com"); - + if (x != 0 || cert == NULL) + return 0; + return !checkCertificate((X509*)cert, "pgw1.trustcommerce.com"); } -- 2.25.1