tclink_gs 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +504 -0
- data/README +98 -0
- data/doc/TC_Link_API_Developer_Guide_5.4.3.pdf +0 -0
- data/ext/tclink/extconf.rb +36 -0
- data/ext/tclink/openssl_management.c +147 -0
- data/ext/tclink/rb_tclink.c +65 -0
- data/ext/tclink/tclink.c +1070 -0
- data/ext/tclink/tclink.h +72 -0
- data/ext/tclink/validate.c +165 -0
- data/lib/tclink.rb +1 -0
- data/lib/tclink/version.rb +3 -0
- metadata +68 -0
data/README
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
Some countries have regulations on the use of cryptographic libraries
|
2
|
+
like the ones embedded in TCLink. It may be unlawful to download TCLink
|
3
|
+
in these countries.
|
4
|
+
|
5
|
+
|
6
|
+
TCLink v4.5.0
|
7
|
+
Ruby Implementation
|
8
|
+
copyright (C) TrustCommerce 2015-2020
|
9
|
+
http://www.trustcommerce.com
|
10
|
+
techsupport@trustcommerce.com
|
11
|
+
|
12
|
+
February 18, 2020
|
13
|
+
|
14
|
+
I. DESCRIPTION
|
15
|
+
|
16
|
+
TCLink is a thin client library to allow your servers to
|
17
|
+
connect to the TrustCommerce payment gateway easily and consistently.
|
18
|
+
The protocol (which is the same across all platforms and languages) is
|
19
|
+
well-documented in the TC Link Developers Guide, so please consult it for
|
20
|
+
any questions you may have about the protocol syntax itself.
|
21
|
+
|
22
|
+
|
23
|
+
II. LICENSE
|
24
|
+
|
25
|
+
TCLink for Ruby is released under the GNU LGPL. Please read LICENSE
|
26
|
+
for details.
|
27
|
+
|
28
|
+
|
29
|
+
III. REQUIREMENTS
|
30
|
+
|
31
|
+
You need to have the OpenSSL development libraries installed. It
|
32
|
+
is recommended that you use the latest version provided by the vendor
|
33
|
+
for PCI reasons.
|
34
|
+
|
35
|
+
Besides the normal Ruby install, you'll need the ruby-devel package,
|
36
|
+
which contains files needed for building Ruby extensions.
|
37
|
+
|
38
|
+
|
39
|
+
IV. BUILDING
|
40
|
+
|
41
|
+
At the root directory of this archive, execute the following:
|
42
|
+
|
43
|
+
./build.sh
|
44
|
+
|
45
|
+
Notes:
|
46
|
+
See below regarding Mac OS X and Homebrew.
|
47
|
+
|
48
|
+
If the module builds without errors, test it with this command:
|
49
|
+
|
50
|
+
ruby tctest.rb
|
51
|
+
|
52
|
+
This script will run a test transaction and print the results.
|
53
|
+
|
54
|
+
|
55
|
+
V. INSTALLATION
|
56
|
+
|
57
|
+
If you have root access to the machine, you will probably want to
|
58
|
+
install TCLink as a global extension. You can do this by copying the
|
59
|
+
extension library (tclink.so) to your Ruby extensions directory, which
|
60
|
+
is typically somewhere under /usr/lib/ruby, such as
|
61
|
+
/usr/lib/ruby/1.6/i386-linux.
|
62
|
+
|
63
|
+
If you can't or don't want to install the module system wide, you can
|
64
|
+
still use in a script by adding an absolute or relative path to the
|
65
|
+
require 'tclink' invocation. For example:
|
66
|
+
|
67
|
+
require '/home/user/tclink'
|
68
|
+
|
69
|
+
|
70
|
+
VI. USAGE
|
71
|
+
|
72
|
+
The tctest.rb script shows a simple example of running transactions
|
73
|
+
through the TCLink API. For further information, please consult the TC
|
74
|
+
Developer's Guide, located in the doc subdirectory.
|
75
|
+
|
76
|
+
VII. PLATFORMS
|
77
|
+
|
78
|
+
The included code has been tested on the following platforms:
|
79
|
+
|
80
|
+
CentOS Linux release 8.1.1911 (Core)
|
81
|
+
OpenSSL Version 1.1.1c-2 (Distribution), Ruby Version 2.6.5 (From Source)
|
82
|
+
OpenSSL Version 1.1.1c-2 (Distribution), ruby-2.5.5-105.module_el8.1.0+214+9be47fd7.x86_64 (Distribution), redhat-rpm-config-120-1.el8.noarch (Distribution)
|
83
|
+
Debian Linux release 8.11
|
84
|
+
OpenSSL Version 1.0.1t-1+deb8u12 (Distribution), Ruby Version 2.1.5-2+deb8u7 (Distribution)
|
85
|
+
Debian Linux release 9.8
|
86
|
+
OpenSSL Version 1.1.0l-1~deb9u1 (Distribution), Ruby Version 2.3.3-1+deb9u7 (Distribution)
|
87
|
+
Debian Linux release 10.3
|
88
|
+
OpenSSL Version 1.1.1d-0+deb10u2 (Distribution), Ruby Version 2.5.5-3+deb10u1 (Distribution)
|
89
|
+
Mac OS 10.13.6
|
90
|
+
OpenSSL Version 1.1.1d (via Homebrew)
|
91
|
+
"./build.sh -d=/usr/local/etc/openssl@1.1/cert.pem"
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
It should work on most modern UNIXes. If you need assistance getting
|
97
|
+
it running on your platform, please email techsupport@trustcommerce.com.
|
98
|
+
|
Binary file
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
File.delete 'config.h' if File.exist? 'config.h'
|
4
|
+
|
5
|
+
ssldir = (`openssl version -d`.chomp.split(/:/)[1]).gsub(' ', '').gsub(/"/, '')
|
6
|
+
certdir = "#{ssldir}/certs"
|
7
|
+
certpath = ENV['SSL_CERT_PATH']
|
8
|
+
|
9
|
+
if certpath.nil?
|
10
|
+
filenames = %w(ca-bundle.crt ca-certificates.crt ca-bundle.trust.crt tls-ca-bundle.pem cert.pem)
|
11
|
+
dirs = [ssldir, certdir]
|
12
|
+
|
13
|
+
filenames.each do |name|
|
14
|
+
dirs.each do |dir|
|
15
|
+
path = "#{dir}/#{name}"
|
16
|
+
if File.exist?(path) & certpath.nil?
|
17
|
+
certpath = path
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# determine the platform name
|
24
|
+
uname=`uname -sm | tr ' ' -`.chomp
|
25
|
+
|
26
|
+
tclink_version = '4.5.0'
|
27
|
+
|
28
|
+
# config.h file sets TCLINK_VERSION with approperiate environment information
|
29
|
+
File.open("config.h", "w") do |f|
|
30
|
+
f.puts "#define TCLINK_VERSION \"#{tclink_version}-Ruby-#{uname}\""
|
31
|
+
f.puts "#define TCLINK_CA_PATH \"#{certpath}\""
|
32
|
+
end
|
33
|
+
|
34
|
+
have_library("crypto", "CRYPTO_lock")
|
35
|
+
have_library("ssl", "SSL_connect")
|
36
|
+
create_makefile("tclink/tclink")
|
@@ -0,0 +1,147 @@
|
|
1
|
+
/**
|
2
|
+
* OpenSSL uses many data structures on which operations must be atomic.
|
3
|
+
* OpenSSL provides for the thread safety of its data structures by requiring
|
4
|
+
* each thread to acquire a mutually exclusive lock known as a mutex that
|
5
|
+
* protects the structure before allowing it to be accessed.
|
6
|
+
* When the thread is finished with the data structure, it releases the mutex,
|
7
|
+
* allowing another thread to acquire the lock and access the data structure.
|
8
|
+
* OpenSSL requires the application programmer to perform these operations in
|
9
|
+
* a manner appropriate for the platform it's running on by making callbacks to
|
10
|
+
* functions that the application registers with OpenSSL for this purpose.
|
11
|
+
*
|
12
|
+
* Use following link for additional details:
|
13
|
+
* 'https://www.openssl.org/docs/man1.0.1/crypto/threads.html'
|
14
|
+
* 'https://wiki.openssl.org/index.php/Library_Initialization'
|
15
|
+
|
16
|
+
* Following code defines and utilizes global mutex array required to make
|
17
|
+
* OpenSSL thread safe. It was adapted from an example
|
18
|
+
* 'crypto/threads/mttest.c' provided with the OpenSSL package.
|
19
|
+
*
|
20
|
+
*/
|
21
|
+
|
22
|
+
#include <assert.h>
|
23
|
+
#include <openssl/crypto.h>
|
24
|
+
#include <openssl/err.h>
|
25
|
+
#include <openssl/ssl.h>
|
26
|
+
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
27
|
+
#include <pthread.h>
|
28
|
+
#endif
|
29
|
+
|
30
|
+
void __attribute__((constructor)) TCLink_OpenSSLInit(void);
|
31
|
+
void __attribute__((destructor)) TCLink_OpenSSLCleanup(void);
|
32
|
+
|
33
|
+
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
34
|
+
static pthread_mutex_t* lock_cs;
|
35
|
+
static long* lock_count;
|
36
|
+
|
37
|
+
static void
|
38
|
+
TCLink_pthreads_thread_id(CRYPTO_THREADID* tid)
|
39
|
+
{
|
40
|
+
CRYPTO_THREADID_set_numeric(tid, (unsigned long)pthread_self());
|
41
|
+
}
|
42
|
+
|
43
|
+
static void
|
44
|
+
TCLink_pthreads_locking_callback(int mode, int type, const char* file, int line)
|
45
|
+
{
|
46
|
+
if (mode & CRYPTO_LOCK) {
|
47
|
+
pthread_mutex_lock(&(lock_cs[type]));
|
48
|
+
lock_count[type]++;
|
49
|
+
} else {
|
50
|
+
pthread_mutex_unlock(&(lock_cs[type]));
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
static void
|
55
|
+
TCLink_thread_setup(void)
|
56
|
+
{
|
57
|
+
int i = 0;
|
58
|
+
lock_cs = OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
|
59
|
+
lock_count = OPENSSL_malloc(CRYPTO_num_locks() * sizeof(long));
|
60
|
+
|
61
|
+
if (!lock_cs || !lock_count) {
|
62
|
+
/* Nothing we can do about this...void function! */
|
63
|
+
if (lock_cs) {
|
64
|
+
OPENSSL_free(lock_cs);
|
65
|
+
lock_cs = NULL;
|
66
|
+
}
|
67
|
+
|
68
|
+
if (lock_count) {
|
69
|
+
OPENSSL_free(lock_count);
|
70
|
+
lock_count = NULL;
|
71
|
+
}
|
72
|
+
|
73
|
+
return;
|
74
|
+
}
|
75
|
+
|
76
|
+
for (i = 0; i < CRYPTO_num_locks(); i++) {
|
77
|
+
lock_count[i] = 0;
|
78
|
+
pthread_mutex_init(&(lock_cs[i]), NULL);
|
79
|
+
}
|
80
|
+
|
81
|
+
CRYPTO_THREADID_set_callback(TCLink_pthreads_thread_id);
|
82
|
+
CRYPTO_set_locking_callback(TCLink_pthreads_locking_callback);
|
83
|
+
}
|
84
|
+
|
85
|
+
static void
|
86
|
+
TCLink_thread_cleanup(void)
|
87
|
+
{
|
88
|
+
int i = 0;
|
89
|
+
|
90
|
+
if (!lock_cs || !lock_count)
|
91
|
+
return;
|
92
|
+
|
93
|
+
CRYPTO_set_locking_callback(NULL);
|
94
|
+
for (i = 0; i < CRYPTO_num_locks(); i++) {
|
95
|
+
pthread_mutex_destroy(&(lock_cs[i]));
|
96
|
+
}
|
97
|
+
|
98
|
+
OPENSSL_free(lock_cs);
|
99
|
+
OPENSSL_free(lock_count);
|
100
|
+
}
|
101
|
+
|
102
|
+
#endif
|
103
|
+
/**
|
104
|
+
*
|
105
|
+
* Initialize the OpenSSL library.
|
106
|
+
* Also sets up static callback functions required for multi-thread safety.
|
107
|
+
*/
|
108
|
+
void
|
109
|
+
TCLink_OpenSSLInit(void)
|
110
|
+
{
|
111
|
+
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
112
|
+
SSL_load_error_strings();
|
113
|
+
assert(SSL_library_init() == 1);
|
114
|
+
TCLink_thread_setup();
|
115
|
+
#else
|
116
|
+
assert(OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CRYPTO_STRINGS |
|
117
|
+
OPENSSL_INIT_ADD_ALL_CIPHERS |
|
118
|
+
OPENSSL_INIT_ADD_ALL_DIGESTS |
|
119
|
+
OPENSSL_INIT_LOAD_CONFIG | OPENSSL_INIT_ASYNC |
|
120
|
+
#ifdef OPENSSL_INIT_NO_ATEXIT
|
121
|
+
OPENSSL_INIT_NO_ATEXIT |
|
122
|
+
#endif
|
123
|
+
#ifdef OPENSSL_INIT_ATFORK
|
124
|
+
OPENSSL_INIT_ATFORK |
|
125
|
+
#endif
|
126
|
+
OPENSSL_INIT_LOAD_SSL_STRINGS,
|
127
|
+
NULL) == 1);
|
128
|
+
#endif
|
129
|
+
}
|
130
|
+
|
131
|
+
/**
|
132
|
+
*
|
133
|
+
* De-initializes the OpenSSL library.
|
134
|
+
* Performs cleanup required for global data structures.
|
135
|
+
*/
|
136
|
+
void
|
137
|
+
TCLink_OpenSSLCleanup(void)
|
138
|
+
{
|
139
|
+
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
140
|
+
TCLink_thread_cleanup();
|
141
|
+
EVP_cleanup();
|
142
|
+
CRYPTO_cleanup_all_ex_data();
|
143
|
+
ERR_free_strings();
|
144
|
+
#else
|
145
|
+
OPENSSL_cleanup();
|
146
|
+
#endif
|
147
|
+
}
|
@@ -0,0 +1,65 @@
|
|
1
|
+
/* ruby_tclink.c - Library code for the TCLink client API. */
|
2
|
+
|
3
|
+
|
4
|
+
#include <ruby.h>
|
5
|
+
#include "tclink.h"
|
6
|
+
|
7
|
+
static VALUE
|
8
|
+
tclink_getversion(VALUE obj) {
|
9
|
+
return rb_str_new2(TCLINK_VERSION);
|
10
|
+
}
|
11
|
+
|
12
|
+
static VALUE
|
13
|
+
tclink_send(VALUE obj, VALUE params) {
|
14
|
+
TCLinkHandle handle;
|
15
|
+
char buf[4096];
|
16
|
+
VALUE input_keys, input_key, input_value, result;
|
17
|
+
char *result_key, *result_value, *next_result_key;
|
18
|
+
int input_keys_count;
|
19
|
+
int i = 0;
|
20
|
+
|
21
|
+
handle = TCLinkCreate();
|
22
|
+
|
23
|
+
/* grab the Ruby hash and stuff each parameter set into TCLink */
|
24
|
+
input_keys = rb_funcall(params, rb_intern("keys"), 0, 0);
|
25
|
+
input_keys_count = FIX2INT(rb_funcall(input_keys, rb_intern("length"),
|
26
|
+
0, 0));
|
27
|
+
|
28
|
+
for (i = 0; i < input_keys_count; i++) {
|
29
|
+
input_key = rb_funcall(input_keys, rb_intern("[]"), 1,
|
30
|
+
INT2FIX(i));
|
31
|
+
input_value = rb_hash_aref(params, input_key);
|
32
|
+
TCLinkPushParam(handle, RSTRING_PTR(StringValue(input_key)),
|
33
|
+
RSTRING_PTR(StringValue(input_value)));
|
34
|
+
}
|
35
|
+
|
36
|
+
/* send the transaction */
|
37
|
+
TCLinkSend(handle);
|
38
|
+
|
39
|
+
/* pull out the returned parameters and put them in a Ruby hash */
|
40
|
+
TCLinkGetEntireResponse(handle, buf, sizeof(buf));
|
41
|
+
|
42
|
+
result = rb_hash_new();
|
43
|
+
result_key = result_value = buf;
|
44
|
+
while (result_key && (result_value = strchr(result_key, '='))) {
|
45
|
+
*result_value++ = 0;
|
46
|
+
next_result_key = strchr(result_value, '\n');
|
47
|
+
if (next_result_key) *next_result_key++ = 0;
|
48
|
+
rb_hash_aset(result, rb_str_new2(result_key),
|
49
|
+
rb_str_new2(result_value));
|
50
|
+
result_key = next_result_key;
|
51
|
+
}
|
52
|
+
|
53
|
+
TCLinkDestroy(handle);
|
54
|
+
|
55
|
+
/* return the results hash */
|
56
|
+
return result;
|
57
|
+
}
|
58
|
+
|
59
|
+
void
|
60
|
+
Init_tclink() {
|
61
|
+
VALUE tclink = rb_define_module("TCLink");
|
62
|
+
|
63
|
+
rb_define_module_function(tclink, "getVersion", tclink_getversion, 0);
|
64
|
+
rb_define_module_function(tclink, "send", tclink_send, 1);
|
65
|
+
}
|
data/ext/tclink/tclink.c
ADDED
@@ -0,0 +1,1070 @@
|
|
1
|
+
/* tclink.c - Library code for the TCLink client API. */
|
2
|
+
|
3
|
+
#include "tclink.h"
|
4
|
+
|
5
|
+
#include <errno.h>
|
6
|
+
#include <memory.h>
|
7
|
+
#include <stdio.h>
|
8
|
+
#include <string.h>
|
9
|
+
|
10
|
+
#ifdef WIN32
|
11
|
+
#include <io.h>
|
12
|
+
#include <winsock2.h>
|
13
|
+
#else
|
14
|
+
#include <arpa/inet.h>
|
15
|
+
#include <netdb.h>
|
16
|
+
#include <netinet/in.h>
|
17
|
+
#include <stdbool.h>
|
18
|
+
#include <strings.h>
|
19
|
+
#include <sys/socket.h>
|
20
|
+
#include <sys/time.h>
|
21
|
+
#include <sys/types.h>
|
22
|
+
#include <unistd.h>
|
23
|
+
#endif
|
24
|
+
|
25
|
+
#include <fcntl.h>
|
26
|
+
#include <signal.h>
|
27
|
+
#include <stdlib.h>
|
28
|
+
#include <sys/stat.h>
|
29
|
+
#include <sys/types.h>
|
30
|
+
|
31
|
+
#include <openssl/crypto.h>
|
32
|
+
#include <openssl/err.h>
|
33
|
+
#include <openssl/pem.h>
|
34
|
+
#include <openssl/rand.h>
|
35
|
+
#include <openssl/ssl.h>
|
36
|
+
#include <openssl/x509.h>
|
37
|
+
|
38
|
+
#ifdef WIN32
|
39
|
+
#define strcasecmp(x, y) stricmp(x, y)
|
40
|
+
#else
|
41
|
+
#define closesocket(x) close(x)
|
42
|
+
#endif
|
43
|
+
|
44
|
+
#define DEFAULT_HOST "pgw1.trustcommerce.com"
|
45
|
+
|
46
|
+
/* changed from forty second to one hundred second to reflect more complicated
|
47
|
+
* transaction processing logic */
|
48
|
+
#define TIMEOUT 100 /* seconds */
|
49
|
+
#define TC_BUFF_MAX 32000
|
50
|
+
#define TC_LINE_MAX ((PARAM_MAX_LEN * 2) + 2)
|
51
|
+
|
52
|
+
char tclink_version[] =
|
53
|
+
TCLINK_VERSION; /* TCLINK_VERSION is defined in Makefile */
|
54
|
+
char tclink_host[] = DEFAULT_HOST;
|
55
|
+
int tclink_port = 443;
|
56
|
+
|
57
|
+
/*************************************************/
|
58
|
+
/* Data structures used only within this module. */
|
59
|
+
/*************************************************/
|
60
|
+
|
61
|
+
/* Variables used for transaction data. */
|
62
|
+
|
63
|
+
typedef struct param_data
|
64
|
+
{
|
65
|
+
char* name;
|
66
|
+
char* value;
|
67
|
+
struct param_data* next;
|
68
|
+
} param;
|
69
|
+
|
70
|
+
typedef struct _TCLinkCon
|
71
|
+
{
|
72
|
+
/* Connection data */
|
73
|
+
int* ip;
|
74
|
+
int num_ips;
|
75
|
+
int sd;
|
76
|
+
|
77
|
+
/* SSL encryption */
|
78
|
+
const SSL_METHOD* meth;
|
79
|
+
long ctx_options;
|
80
|
+
SSL_CTX* ctx;
|
81
|
+
SSL* ssl;
|
82
|
+
|
83
|
+
/* Transaction parameters, sent and received */
|
84
|
+
param *send_param_list, *send_param_tail;
|
85
|
+
param* recv_param_list;
|
86
|
+
|
87
|
+
/* Connection status */
|
88
|
+
int is_error;
|
89
|
+
int pass;
|
90
|
+
time_t start_time;
|
91
|
+
int dns;
|
92
|
+
|
93
|
+
int (*validate_cert)(int, void*);
|
94
|
+
int full_ssl_close;
|
95
|
+
|
96
|
+
} TCLinkCon;
|
97
|
+
|
98
|
+
/*************************************
|
99
|
+
* Internal functions, not exported. *
|
100
|
+
*************************************/
|
101
|
+
|
102
|
+
/* Random number from min to max. */
|
103
|
+
static int
|
104
|
+
number(int min, int max)
|
105
|
+
{
|
106
|
+
time_t t = time(0);
|
107
|
+
return (rand_r((unsigned int*)&t) % (max - min + 1)) + min;
|
108
|
+
}
|
109
|
+
|
110
|
+
/* Check if path points to a regular file */
|
111
|
+
int
|
112
|
+
is_regular_file(const char* path)
|
113
|
+
{
|
114
|
+
struct stat st;
|
115
|
+
stat(path, &st);
|
116
|
+
return S_ISREG(st.st_mode);
|
117
|
+
}
|
118
|
+
|
119
|
+
/* Safe string copy and append functions. */
|
120
|
+
#define SAFE_COPY(d, s) safe_copy((d), (s), sizeof(d));
|
121
|
+
#define SAFE_APPEND(d, s) safe_append((d), (s), sizeof(d));
|
122
|
+
|
123
|
+
static bool
|
124
|
+
safe_copy(char* dst, const char* src, int size)
|
125
|
+
{
|
126
|
+
int len = (int) strlen(src);
|
127
|
+
if (len < size)
|
128
|
+
strcpy(dst, src);
|
129
|
+
else {
|
130
|
+
strncpy(dst, src, size - 1);
|
131
|
+
dst[size - 1] = 0;
|
132
|
+
}
|
133
|
+
return (len >= size);
|
134
|
+
}
|
135
|
+
|
136
|
+
static bool
|
137
|
+
safe_append(char* dst, const char* src, int size)
|
138
|
+
{
|
139
|
+
int dlen = (int) strlen(dst);
|
140
|
+
int slen = (int) strlen(src);
|
141
|
+
int avail = size - dlen;
|
142
|
+
if (avail < 1)
|
143
|
+
return true;
|
144
|
+
|
145
|
+
if (slen < avail)
|
146
|
+
strcpy(dst + dlen, src);
|
147
|
+
else {
|
148
|
+
strncpy(dst + dlen, src, avail - 1);
|
149
|
+
dst[size - 1] = 0;
|
150
|
+
}
|
151
|
+
return (slen >= avail);
|
152
|
+
}
|
153
|
+
|
154
|
+
/* Add a parameter-value pair to the recieved list. */
|
155
|
+
static void
|
156
|
+
AddRecvParam(TCLinkCon* c, const char* name, const char* value)
|
157
|
+
{
|
158
|
+
param* p;
|
159
|
+
|
160
|
+
if (name[0] == 0 || value[0] == 0)
|
161
|
+
return;
|
162
|
+
|
163
|
+
p = (param*)malloc(sizeof(param));
|
164
|
+
p->name = strdup(name);
|
165
|
+
p->value = strdup(value);
|
166
|
+
p->next = c->recv_param_list;
|
167
|
+
c->recv_param_list = p;
|
168
|
+
}
|
169
|
+
|
170
|
+
/* Add a string to the received list. */
|
171
|
+
static int
|
172
|
+
AddRecvString(TCLinkCon* c, char* string)
|
173
|
+
{
|
174
|
+
char* ptr = strchr(string, '=');
|
175
|
+
if (ptr == NULL)
|
176
|
+
return 0;
|
177
|
+
|
178
|
+
*ptr = 0;
|
179
|
+
AddRecvParam(c, string, ptr + 1);
|
180
|
+
|
181
|
+
return 1;
|
182
|
+
}
|
183
|
+
|
184
|
+
/* Deallocate the send list. */
|
185
|
+
static void
|
186
|
+
ClearSendList(TCLinkCon* c)
|
187
|
+
{
|
188
|
+
param *p, *next;
|
189
|
+
for (p = c->send_param_list; p; p = next) {
|
190
|
+
next = p->next;
|
191
|
+
free(p->name);
|
192
|
+
free(p->value);
|
193
|
+
free(p);
|
194
|
+
}
|
195
|
+
|
196
|
+
c->send_param_list = c->send_param_tail = NULL;
|
197
|
+
}
|
198
|
+
|
199
|
+
/* Deallocate the recv list. */
|
200
|
+
static void
|
201
|
+
ClearRecvList(TCLinkCon* c)
|
202
|
+
{
|
203
|
+
param *p, *next;
|
204
|
+
for (p = c->recv_param_list; p; p = next) {
|
205
|
+
next = p->next;
|
206
|
+
free(p->name);
|
207
|
+
free(p->value);
|
208
|
+
free(p);
|
209
|
+
}
|
210
|
+
|
211
|
+
c->recv_param_list = NULL;
|
212
|
+
}
|
213
|
+
|
214
|
+
void
|
215
|
+
do_SSL_randomize(void)
|
216
|
+
{
|
217
|
+
enum
|
218
|
+
{
|
219
|
+
RAND_VALS = 32
|
220
|
+
};
|
221
|
+
int randbuf[RAND_VALS];
|
222
|
+
char fname[512];
|
223
|
+
int use_rand_file;
|
224
|
+
time_t t;
|
225
|
+
int i, c;
|
226
|
+
|
227
|
+
/* if they have a /dev/urandom we can skip this function */
|
228
|
+
if (RAND_status() != 0)
|
229
|
+
return;
|
230
|
+
|
231
|
+
t = time(0);
|
232
|
+
RAND_seed((char*)&t, sizeof(time_t));
|
233
|
+
|
234
|
+
/* have they specified a random file with RANDFILE environment variable? */
|
235
|
+
use_rand_file = RAND_file_name(fname, sizeof(fname)) ? 1 : 0;
|
236
|
+
if (use_rand_file)
|
237
|
+
RAND_load_file(fname, 4096);
|
238
|
+
|
239
|
+
/* stuff it with packets of random numbers until it is satisfied */
|
240
|
+
for (i = 0; i < 256 && RAND_status() == 0; i++) {
|
241
|
+
for (c = 0; c < RAND_VALS; c++)
|
242
|
+
randbuf[c] = rand_r((unsigned int*)&t);
|
243
|
+
RAND_seed((char*)randbuf, sizeof(int) * RAND_VALS);
|
244
|
+
}
|
245
|
+
}
|
246
|
+
|
247
|
+
/* Make sure all of the ssl objects are properly initialized.
|
248
|
+
*/
|
249
|
+
static int
|
250
|
+
init_ssl(TCLinkCon* c)
|
251
|
+
{
|
252
|
+
int ret = 1;
|
253
|
+
|
254
|
+
/* Sanity Check */
|
255
|
+
if (c == NULL)
|
256
|
+
return 0;
|
257
|
+
|
258
|
+
/* do some SSL setup */
|
259
|
+
if (!c->meth) {
|
260
|
+
do_SSL_randomize(); /* handle systems without /dev/urandom */
|
261
|
+
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
262
|
+
c->meth = SSLv23_client_method();
|
263
|
+
c->ctx_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1;
|
264
|
+
#else
|
265
|
+
c->meth = TLS_client_method();
|
266
|
+
c->ctx_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 |
|
267
|
+
SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TICKET;
|
268
|
+
#endif
|
269
|
+
}
|
270
|
+
|
271
|
+
if (!c->ctx) {
|
272
|
+
int val;
|
273
|
+
int is_file;
|
274
|
+
|
275
|
+
c->ctx = SSL_CTX_new(c->meth);
|
276
|
+
if (!c->ctx)
|
277
|
+
return 0;
|
278
|
+
/* set options */
|
279
|
+
if (c->ctx_options)
|
280
|
+
SSL_CTX_set_options(c->ctx, c->ctx_options);
|
281
|
+
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
|
282
|
+
SSL_CTX_set_min_proto_version(c->ctx, TLS1_2_VERSION);
|
283
|
+
#endif
|
284
|
+
|
285
|
+
is_file = is_regular_file(TCLINK_CA_PATH);
|
286
|
+
val = SSL_CTX_load_verify_locations(
|
287
|
+
c->ctx, is_file ? TCLINK_CA_PATH : NULL, is_file ? NULL : TCLINK_CA_PATH);
|
288
|
+
|
289
|
+
if (!val)
|
290
|
+
return 0; // failed to populate cert store
|
291
|
+
|
292
|
+
/* turn on certificate chain validation */
|
293
|
+
SSL_CTX_set_verify(c->ctx, SSL_VERIFY_PEER, NULL);
|
294
|
+
}
|
295
|
+
|
296
|
+
if (!c->ssl) {
|
297
|
+
c->ssl = SSL_new(c->ctx);
|
298
|
+
if (!c->ssl) {
|
299
|
+
SSL_CTX_free(c->ctx);
|
300
|
+
c->ctx = NULL;
|
301
|
+
return 0;
|
302
|
+
}
|
303
|
+
}
|
304
|
+
|
305
|
+
return ret;
|
306
|
+
}
|
307
|
+
|
308
|
+
/* Open a socket to the host_ip specified. Returns the socket's file
|
309
|
+
* descriptor on success (the open attempt is underway) or -1 for failure
|
310
|
+
* (should never happen in practice). Note that this function DOES NOT block
|
311
|
+
* and wait for the connection; you'll need to select() on the socket later to
|
312
|
+
* see if it opened successfully.
|
313
|
+
*/
|
314
|
+
static int
|
315
|
+
BeginConnection(TCLinkCon* c, int host_ip)
|
316
|
+
{
|
317
|
+
struct sockaddr_in sa;
|
318
|
+
int sd;
|
319
|
+
|
320
|
+
sd = socket(AF_INET, SOCK_STREAM, 0);
|
321
|
+
if (sd < 0)
|
322
|
+
return -1;
|
323
|
+
|
324
|
+
#ifdef WIN32
|
325
|
+
u_long param = 1;
|
326
|
+
ioctlsocket(sd, FIONBIO, ¶m);
|
327
|
+
#else
|
328
|
+
fcntl(sd, F_SETFL, O_NONBLOCK);
|
329
|
+
#endif
|
330
|
+
|
331
|
+
memset(&sa, 0, sizeof(sa));
|
332
|
+
sa.sin_family = AF_INET;
|
333
|
+
sa.sin_addr.s_addr = host_ip;
|
334
|
+
sa.sin_port = htons(tclink_port);
|
335
|
+
|
336
|
+
connect(sd, (struct sockaddr*)&sa, sizeof(sa));
|
337
|
+
|
338
|
+
return sd;
|
339
|
+
}
|
340
|
+
|
341
|
+
/* This function is called on a socket file descriptor once the connection has
|
342
|
+
* been established and we're ready to negotiate SSL. If the SSL handshake
|
343
|
+
* fails for some reason (such as the host on the other end not using SSL), it
|
344
|
+
* will return 0 for failure. Success returns 1.
|
345
|
+
*/
|
346
|
+
static int
|
347
|
+
FinishConnection(TCLinkCon* c, int sd)
|
348
|
+
{
|
349
|
+
int ssl_connected, is_error, errcode, res;
|
350
|
+
X509* server_cert;
|
351
|
+
time_t start, remaining;
|
352
|
+
fd_set in, out, err;
|
353
|
+
struct timeval tv;
|
354
|
+
|
355
|
+
/* check if socket has connected successfully */
|
356
|
+
int val;
|
357
|
+
socklen_t size = 4;
|
358
|
+
getsockopt(sd, SOL_SOCKET, SO_ERROR, (char*)&val, &size);
|
359
|
+
if (val != 0)
|
360
|
+
return 0;
|
361
|
+
|
362
|
+
if (c->ssl)
|
363
|
+
SSL_clear(c->ssl);
|
364
|
+
|
365
|
+
ERR_clear_error();
|
366
|
+
|
367
|
+
// Call init_ssl to make sure everything is setup properly.
|
368
|
+
if (!init_ssl(c))
|
369
|
+
return 0;
|
370
|
+
|
371
|
+
SSL_set_fd(c->ssl, sd);
|
372
|
+
|
373
|
+
ssl_connected = 0;
|
374
|
+
is_error = 0;
|
375
|
+
start = time(0);
|
376
|
+
|
377
|
+
while (!ssl_connected && !is_error) {
|
378
|
+
|
379
|
+
remaining = 5 - (time(0) - start);
|
380
|
+
if (remaining <= 0) {
|
381
|
+
is_error = 1;
|
382
|
+
break;
|
383
|
+
}
|
384
|
+
|
385
|
+
res = SSL_connect(c->ssl);
|
386
|
+
|
387
|
+
ssl_connected = ((res == 1) && SSL_is_init_finished(c->ssl));
|
388
|
+
|
389
|
+
if (!ssl_connected) {
|
390
|
+
FD_ZERO(&in);
|
391
|
+
FD_SET((unsigned)sd, &in);
|
392
|
+
FD_ZERO(&out);
|
393
|
+
FD_SET((unsigned)sd, &out);
|
394
|
+
FD_ZERO(&err);
|
395
|
+
FD_SET((unsigned)sd, &err);
|
396
|
+
/* the documentation does not suggest that both error types occur at the
|
397
|
+
* same time so the retry logic will consume all the outstanding events we
|
398
|
+
* do not actually use oob data, but if it is sent, it is treated as an
|
399
|
+
* error all the same
|
400
|
+
*/
|
401
|
+
errcode = SSL_get_error(c->ssl, res);
|
402
|
+
switch (errcode) {
|
403
|
+
case SSL_ERROR_NONE:
|
404
|
+
/* no error, we should have a connection, check again */
|
405
|
+
break;
|
406
|
+
|
407
|
+
case SSL_ERROR_WANT_READ:
|
408
|
+
/* no error, just wait for more data */
|
409
|
+
tv.tv_sec = remaining;
|
410
|
+
tv.tv_usec = 0;
|
411
|
+
/* posix-2001 says the function will modify the appropriate
|
412
|
+
* descriptors */
|
413
|
+
if (select(sd + 1, &in, NULL, &err, &tv) < 0 ||
|
414
|
+
FD_ISSET((unsigned)sd, &err))
|
415
|
+
is_error = 1;
|
416
|
+
break;
|
417
|
+
case SSL_ERROR_WANT_WRITE:
|
418
|
+
/* no error, just wait for more data */
|
419
|
+
tv.tv_sec = remaining;
|
420
|
+
tv.tv_usec = 0;
|
421
|
+
if (select(sd + 1, NULL, &out, &err, &tv) < 0 ||
|
422
|
+
FD_ISSET((unsigned)sd, &err))
|
423
|
+
is_error = 1;
|
424
|
+
break;
|
425
|
+
case SSL_ERROR_ZERO_RETURN: /* peer closed the connection */
|
426
|
+
case SSL_ERROR_SSL: /* error in SSL handshake */
|
427
|
+
default:
|
428
|
+
is_error = 1;
|
429
|
+
}
|
430
|
+
}
|
431
|
+
}
|
432
|
+
|
433
|
+
if (is_error) {
|
434
|
+
return 0;
|
435
|
+
}
|
436
|
+
|
437
|
+
#ifdef WIN32
|
438
|
+
u_long param = 0;
|
439
|
+
ioctlsocket(sd, FIONBIO, ¶m); // make the socket blocking again
|
440
|
+
#else
|
441
|
+
fcntl(sd, F_SETFL, 0); /* make the socket blocking again */
|
442
|
+
#endif
|
443
|
+
|
444
|
+
/* verify that server certificate is authentic */
|
445
|
+
server_cert = SSL_get_peer_certificate(c->ssl);
|
446
|
+
if (!server_cert) {
|
447
|
+
return 0;
|
448
|
+
}
|
449
|
+
if (c->validate_cert && c->validate_cert(0, server_cert) != 0) {
|
450
|
+
X509_free(server_cert);
|
451
|
+
return 0;
|
452
|
+
}
|
453
|
+
X509_free(server_cert);
|
454
|
+
|
455
|
+
return 1;
|
456
|
+
}
|
457
|
+
|
458
|
+
/* This function should be called on list of socket file descriptors (sd) to
|
459
|
+
* determine if any have opened successfully. If so, it will return which one
|
460
|
+
* (index into the array). Otherwise it returns -1 if none have successfully
|
461
|
+
* opened. This function will block for a maximum of 3 seconds. As this function
|
462
|
+
* calls FinishConnection(), you shouldn't need to do anything special after it
|
463
|
+
* returns success - the socket is set up and ready for use.
|
464
|
+
*/
|
465
|
+
static int
|
466
|
+
CheckConnection(TCLinkCon* c, int* sd, int num_sd)
|
467
|
+
{
|
468
|
+
fd_set wr_set, err_set;
|
469
|
+
struct timeval tv;
|
470
|
+
int max_sd = -1, i;
|
471
|
+
|
472
|
+
tv.tv_sec = 3; /* wait 3 seconds for soc->mething to happen */
|
473
|
+
tv.tv_usec = 0;
|
474
|
+
|
475
|
+
/* build the fd_sets used for select() */
|
476
|
+
FD_ZERO(&wr_set);
|
477
|
+
FD_ZERO(&err_set);
|
478
|
+
for (i = 0; i < num_sd; i++) {
|
479
|
+
if (sd[i] < 0)
|
480
|
+
continue;
|
481
|
+
FD_SET(sd[i], &wr_set);
|
482
|
+
FD_SET(sd[i], &err_set);
|
483
|
+
if (sd[i] > max_sd)
|
484
|
+
max_sd = sd[i];
|
485
|
+
}
|
486
|
+
|
487
|
+
/* run the select and see what we have waiting for us */
|
488
|
+
if (select(max_sd + 1, NULL, &wr_set, &err_set, &tv) < 1)
|
489
|
+
return -1; /* I hope this never happens */
|
490
|
+
|
491
|
+
for (i = 0; i < num_sd; i++)
|
492
|
+
if (sd[i] >= 0) {
|
493
|
+
if (FD_ISSET(sd[i], &err_set)) {
|
494
|
+
/* error - close the socket and mark it defunct */
|
495
|
+
close(sd[i]);
|
496
|
+
sd[i] = -1;
|
497
|
+
} else if (FD_ISSET(sd[i], &wr_set)) {
|
498
|
+
/* socket has opened! try to negotiate SSL */
|
499
|
+
if (FinishConnection(c, sd[i])) {
|
500
|
+
/* socket is ready to go, so return success */
|
501
|
+
c->sd = sd[i];
|
502
|
+
return i;
|
503
|
+
} else {
|
504
|
+
/* SSL handshake had errors, close the socket and mark it defunct */
|
505
|
+
close(sd[i]);
|
506
|
+
sd[i] = -1;
|
507
|
+
// Clear the ssl environment too.
|
508
|
+
if (c->ssl) {
|
509
|
+
SSL_free(c->ssl);
|
510
|
+
c->ssl = NULL;
|
511
|
+
}
|
512
|
+
}
|
513
|
+
}
|
514
|
+
}
|
515
|
+
|
516
|
+
/* if we get here, nothing much interesting happened during those 3 seconds */
|
517
|
+
return -1;
|
518
|
+
}
|
519
|
+
|
520
|
+
/* Open a connection to one of the TrustCommerce gateway servers. */
|
521
|
+
static int
|
522
|
+
Connect(TCLinkCon* c, int host_hash)
|
523
|
+
{
|
524
|
+
struct hostent default_he;
|
525
|
+
char* addr_list[3];
|
526
|
+
int addr[2];
|
527
|
+
unsigned int** gw = NULL;
|
528
|
+
|
529
|
+
enum
|
530
|
+
{
|
531
|
+
MAX_HOSTS = 32
|
532
|
+
};
|
533
|
+
time_t last_connect[MAX_HOSTS];
|
534
|
+
int sd[MAX_HOSTS];
|
535
|
+
int num_sd = 0;
|
536
|
+
int host;
|
537
|
+
|
538
|
+
int i, j, sort, sort_val;
|
539
|
+
|
540
|
+
for (i = 0; i < MAX_HOSTS; i++)
|
541
|
+
sd[i] = -1;
|
542
|
+
|
543
|
+
c->sd = -1;
|
544
|
+
c->is_error = 0;
|
545
|
+
|
546
|
+
srand((unsigned) time(0));
|
547
|
+
|
548
|
+
/* These are used as BACKUP ONLY if the DNS if offline. */
|
549
|
+
addr[0] = inet_addr("206.82.213.130");
|
550
|
+
addr[1] = inet_addr("208.72.241.130");
|
551
|
+
addr_list[0] = (char*)&addr[0];
|
552
|
+
addr_list[1] = (char*)&addr[1];
|
553
|
+
addr_list[2] = 0;
|
554
|
+
default_he.h_addr_list = addr_list;
|
555
|
+
|
556
|
+
/* determine IP addresses of gateway */
|
557
|
+
if (!c->ip) {
|
558
|
+
#ifndef __APPLE__
|
559
|
+
int herr = 0;
|
560
|
+
char tmpbuf[4096];
|
561
|
+
struct hostent tmpres;
|
562
|
+
#endif
|
563
|
+
struct hostent * he = NULL;
|
564
|
+
int ret = -1;
|
565
|
+
|
566
|
+
#ifdef __APPLE__
|
567
|
+
he = gethostbyname(tclink_host);
|
568
|
+
if (he)
|
569
|
+
ret = 0;
|
570
|
+
#else
|
571
|
+
ret =
|
572
|
+
gethostbyname_r(tclink_host, &tmpres, tmpbuf, sizeof(tmpbuf), &he, &herr);
|
573
|
+
if (ret)
|
574
|
+
he = NULL;
|
575
|
+
|
576
|
+
#endif
|
577
|
+
if (ret == 0 && he != NULL) {
|
578
|
+
c->dns = 1;
|
579
|
+
} else {
|
580
|
+
/* fall back to hardcoded IPs in an emergency */
|
581
|
+
c->dns = 0;
|
582
|
+
he = &default_he;
|
583
|
+
}
|
584
|
+
|
585
|
+
for (c->num_ips = 0; he->h_addr_list[c->num_ips]; c->num_ips++)
|
586
|
+
;
|
587
|
+
|
588
|
+
c->ip = (int*)malloc(c->num_ips * sizeof(int));
|
589
|
+
gw = (int unsigned**)he->h_addr_list;
|
590
|
+
|
591
|
+
/* sort the IP address list before storing it */
|
592
|
+
for (i = 0; i < c->num_ips; i++) {
|
593
|
+
sort = 0;
|
594
|
+
sort_val = *gw[0];
|
595
|
+
for (j = 1; j < c->num_ips; j++)
|
596
|
+
if (*gw[j] > (unsigned int)sort_val) {
|
597
|
+
sort = j;
|
598
|
+
sort_val = *gw[j];
|
599
|
+
}
|
600
|
+
|
601
|
+
c->ip[i] = sort_val;
|
602
|
+
*gw[sort] = 0;
|
603
|
+
}
|
604
|
+
}
|
605
|
+
|
606
|
+
if (!init_ssl(c))
|
607
|
+
return 0;
|
608
|
+
|
609
|
+
/* This loop works as follows:
|
610
|
+
* Grab the first host. Try to open a connection to it. If there was an
|
611
|
+
* error (host down or unreachable) go to the next one. If nothing has
|
612
|
+
* happened after 3 seconds, open a second socket (the first one is still
|
613
|
+
* open!) and try with the next fail-over host. Continue to do this for a
|
614
|
+
* maximum of MAX_HOSTS sockets, or until our TIMEOUT value runs out. We also
|
615
|
+
* keep track of how recently we tried to connect to a given host, so that we
|
616
|
+
* avoid saturating the machines in a heavy-load situation (which could be
|
617
|
+
* caused by anything from heavy internet lag between the local host and the
|
618
|
+
* TrustCommerce servers, to heavy load on the servers themselves due to half
|
619
|
+
* a million people trying to run credit card transactions in the same half
|
620
|
+
* second - unlikely, but certainly possible.)
|
621
|
+
*/
|
622
|
+
c->start_time = time(0);
|
623
|
+
c->pass = 1;
|
624
|
+
memset(last_connect, 0, MAX_HOSTS * sizeof(time_t));
|
625
|
+
|
626
|
+
host = host_hash % c->num_ips;
|
627
|
+
|
628
|
+
for (; time(0) < (c->start_time + TIMEOUT); c->pass++) {
|
629
|
+
/* retry the first host at least once */
|
630
|
+
if (c->pass > 2)
|
631
|
+
host += 1;
|
632
|
+
if (host >= c->num_ips)
|
633
|
+
host = 0;
|
634
|
+
|
635
|
+
/* only connect if we haven't tried this host before, or it's been a little
|
636
|
+
* while (note random modifier to help stagger network traffic) */
|
637
|
+
if (last_connect[host] == 0 ||
|
638
|
+
(time(0) - last_connect[host]) >= number(TIMEOUT / 4, TIMEOUT)) {
|
639
|
+
if (num_sd < MAX_HOSTS) {
|
640
|
+
/* fire up a new connection to this host */
|
641
|
+
if (c->pass != 1)
|
642
|
+
last_connect[host] = time(0);
|
643
|
+
|
644
|
+
sd[num_sd] = BeginConnection(c, c->ip[host]);
|
645
|
+
if (sd[num_sd] >= 0)
|
646
|
+
num_sd++;
|
647
|
+
}
|
648
|
+
}
|
649
|
+
|
650
|
+
/* scan all current sockets and see if we've made a successful connection
|
651
|
+
* somewhere. note that this also includes SSL and all that sort of fun,
|
652
|
+
* so once it returns success, we're all done. */
|
653
|
+
if (num_sd > 0) {
|
654
|
+
if (CheckConnection(c, sd, num_sd) >= 0) {
|
655
|
+
/* Success: close all other file handles and return */
|
656
|
+
for (i = 0; i < num_sd; i++)
|
657
|
+
if (sd[i] >= 0 && sd[i] != c->sd)
|
658
|
+
close(sd[i]);
|
659
|
+
|
660
|
+
return 1;
|
661
|
+
}
|
662
|
+
}
|
663
|
+
|
664
|
+
usleep(1000); // sleep for 1 millisecond
|
665
|
+
}
|
666
|
+
|
667
|
+
// We couldn't connect successfully to any endpoint/s.
|
668
|
+
// Close any open sockets to avoid leaks.
|
669
|
+
for (i = 0; i < num_sd; i++) {
|
670
|
+
if (sd[i] >= 0) {
|
671
|
+
close(sd[i]);
|
672
|
+
sd[i] = -1;
|
673
|
+
}
|
674
|
+
}
|
675
|
+
|
676
|
+
return 0;
|
677
|
+
}
|
678
|
+
|
679
|
+
/* Send a chunk of data through a connection previously opened with Connect().
|
680
|
+
*/
|
681
|
+
static int
|
682
|
+
Send(TCLinkCon* c, const char* string)
|
683
|
+
{
|
684
|
+
if (SSL_write(c->ssl, string, (unsigned) strlen(string)) < 0)
|
685
|
+
return 0;
|
686
|
+
|
687
|
+
return 1;
|
688
|
+
}
|
689
|
+
|
690
|
+
/* Peel a line off the current input. Note that this DOESN'T necessarily wait
|
691
|
+
* for all input to come in, only up to a "\n". -1 is returned for a network
|
692
|
+
* error, otherwise it returns the length of the line read. If there is not a
|
693
|
+
* complete line pending for read this will block until there is, or an error
|
694
|
+
* occurs.
|
695
|
+
*/
|
696
|
+
static int
|
697
|
+
ReadLine(TCLinkCon* c, char* buffer, char* destbuf)
|
698
|
+
{
|
699
|
+
struct timeval tv;
|
700
|
+
fd_set read;
|
701
|
+
fd_set error;
|
702
|
+
int sel;
|
703
|
+
|
704
|
+
while (1) /* we wait for a line to come in or an error to occur */
|
705
|
+
{
|
706
|
+
char* eol = strchr(buffer, '\n');
|
707
|
+
if (eol != NULL) {
|
708
|
+
/* peel off the line and return it */
|
709
|
+
*eol++ = 0;
|
710
|
+
safe_copy(destbuf, buffer, TC_LINE_MAX);
|
711
|
+
memmove(buffer, eol, strlen(eol) + 1);
|
712
|
+
return (int) strlen(destbuf);
|
713
|
+
} else {
|
714
|
+
if (c->is_error == 1)
|
715
|
+
return -1;
|
716
|
+
|
717
|
+
/* do socket work to grab the most recent chunk of incoming data */
|
718
|
+
FD_ZERO(&read);
|
719
|
+
FD_SET(c->sd, &read);
|
720
|
+
FD_ZERO(&error);
|
721
|
+
FD_SET(c->sd, &error);
|
722
|
+
tv.tv_sec = TIMEOUT;
|
723
|
+
tv.tv_usec = 0;
|
724
|
+
|
725
|
+
sel = select(c->sd + 1, &read, NULL, &error, &tv);
|
726
|
+
if (sel < 1)
|
727
|
+
c->is_error = 1;
|
728
|
+
else if (FD_ISSET(c->sd, &error))
|
729
|
+
c->is_error = 1;
|
730
|
+
else if (FD_ISSET(c->sd, &read)) {
|
731
|
+
int buffer_end = (int) strlen(buffer);
|
732
|
+
int size =
|
733
|
+
SSL_read(c->ssl, buffer + buffer_end, TC_BUFF_MAX - 1 - buffer_end);
|
734
|
+
if (size == 0) {
|
735
|
+
int error_type = SSL_get_error(c->ssl, size);
|
736
|
+
switch (error_type) {
|
737
|
+
/* this would never happen in practice */
|
738
|
+
case SSL_ERROR_NONE:
|
739
|
+
/* this wouldn't happen either because the ssl transport is blocking
|
740
|
+
*/
|
741
|
+
case SSL_ERROR_WANT_READ:
|
742
|
+
case SSL_ERROR_WANT_WRITE:
|
743
|
+
buffer[buffer_end] = 0;
|
744
|
+
break;
|
745
|
+
|
746
|
+
/* these others should not really happen but if they do, we bail */
|
747
|
+
/* we would never get any more data and it looks like the callee is
|
748
|
+
* expecting something */
|
749
|
+
case SSL_ERROR_ZERO_RETURN:
|
750
|
+
case SSL_ERROR_WANT_CONNECT:
|
751
|
+
case SSL_ERROR_WANT_ACCEPT:
|
752
|
+
case SSL_ERROR_SYSCALL:
|
753
|
+
case SSL_ERROR_WANT_X509_LOOKUP:
|
754
|
+
case SSL_ERROR_SSL:
|
755
|
+
default:
|
756
|
+
c->is_error = 1;
|
757
|
+
break;
|
758
|
+
}
|
759
|
+
} else if (size < 0)
|
760
|
+
c->is_error = 1;
|
761
|
+
else
|
762
|
+
buffer[buffer_end + size] = 0;
|
763
|
+
}
|
764
|
+
}
|
765
|
+
}
|
766
|
+
}
|
767
|
+
|
768
|
+
/* Closes a connection opened with Connect() and frees memory associated with
|
769
|
+
* it. You ONLY need to Close() connections which opened successfully; those
|
770
|
+
* that don't clean up after themselves before Connect() returns.
|
771
|
+
*/
|
772
|
+
static int
|
773
|
+
Close(TCLinkCon* c)
|
774
|
+
{
|
775
|
+
if (c->ssl) {
|
776
|
+
/* The full shutdown presented here is more for completeness than necessity;
|
777
|
+
* at this point in the application, we have already received the end
|
778
|
+
* trailer (or bust) which is generally accompanied by a close notify
|
779
|
+
* message. If the software chooses to respond to the close notify (per TLS
|
780
|
+
* specification) this would result in at least reading the incoming close
|
781
|
+
* notify and issuing our own. Because this entails an additional round
|
782
|
+
* trip that is not needed (the transaction is done after the accompanying
|
783
|
+
* END), there does not appear to be a benefit to it at all. By default
|
784
|
+
* though, this configuration is enabled and can be disabled by the
|
785
|
+
* integrator for performance reasons.
|
786
|
+
*/
|
787
|
+
if (c->full_ssl_close) {
|
788
|
+
int status = SSL_shutdown(c->ssl);
|
789
|
+
if (status == 0)
|
790
|
+
status = SSL_shutdown(c->ssl);
|
791
|
+
} else
|
792
|
+
SSL_set_shutdown(c->ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
|
793
|
+
}
|
794
|
+
|
795
|
+
if (c->sd >= 0) {
|
796
|
+
close(c->sd);
|
797
|
+
c->sd = -1;
|
798
|
+
}
|
799
|
+
|
800
|
+
return 1;
|
801
|
+
}
|
802
|
+
|
803
|
+
static void
|
804
|
+
stuff_string(char* buf, int* len, int size, const char* add)
|
805
|
+
{
|
806
|
+
int newlen = (int) strlen(add);
|
807
|
+
if ((*len + newlen) >= size)
|
808
|
+
newlen = size - *len - 1;
|
809
|
+
if (newlen < 1)
|
810
|
+
return;
|
811
|
+
strncpy(buf + *len, add, newlen);
|
812
|
+
*len += newlen;
|
813
|
+
buf[*len] = 0;
|
814
|
+
}
|
815
|
+
|
816
|
+
/**********************************************
|
817
|
+
* API functions exported to the user client. *
|
818
|
+
**********************************************/
|
819
|
+
|
820
|
+
TCLinkHandle
|
821
|
+
TCLinkCreate()
|
822
|
+
{
|
823
|
+
extern int TCLinkDefaultValidate(int, void*);
|
824
|
+
|
825
|
+
TCLinkCon* c = (TCLinkCon*)malloc(sizeof(TCLinkCon));
|
826
|
+
|
827
|
+
c->ip = NULL;
|
828
|
+
c->num_ips = 0;
|
829
|
+
c->sd = -1;
|
830
|
+
|
831
|
+
c->meth = NULL;
|
832
|
+
c->ctx_options = 0;
|
833
|
+
c->ctx = NULL;
|
834
|
+
c->ssl = NULL;
|
835
|
+
|
836
|
+
c->send_param_list = NULL;
|
837
|
+
c->send_param_tail = NULL;
|
838
|
+
c->recv_param_list = NULL;
|
839
|
+
|
840
|
+
c->is_error = 0;
|
841
|
+
c->pass = 0;
|
842
|
+
c->start_time = 0;
|
843
|
+
c->dns = -1;
|
844
|
+
|
845
|
+
c->validate_cert = TCLinkDefaultValidate;
|
846
|
+
c->full_ssl_close = 1;
|
847
|
+
|
848
|
+
return (TCLinkHandle)c;
|
849
|
+
}
|
850
|
+
|
851
|
+
int
|
852
|
+
TCLinkSetFullClose(TCLinkHandle handle, int full_ssl_close)
|
853
|
+
{
|
854
|
+
TCLinkCon* c = (TCLinkCon*)handle;
|
855
|
+
int swap = c->full_ssl_close;
|
856
|
+
c->full_ssl_close = full_ssl_close ? 1 : 0;
|
857
|
+
return swap;
|
858
|
+
}
|
859
|
+
|
860
|
+
void
|
861
|
+
TCLinkSetValidateCallback(TCLinkHandle handle, int (*validate_cert)(int, void*))
|
862
|
+
{
|
863
|
+
TCLinkCon* c = (TCLinkCon*)handle;
|
864
|
+
if (validate_cert == NULL) {
|
865
|
+
extern int TCLinkDefaultValidate(int, void*);
|
866
|
+
c->validate_cert = TCLinkDefaultValidate;
|
867
|
+
} else
|
868
|
+
c->validate_cert = validate_cert;
|
869
|
+
}
|
870
|
+
|
871
|
+
void
|
872
|
+
TCLinkPushParam(TCLinkHandle handle, const char* name, const char* value)
|
873
|
+
{
|
874
|
+
param* p;
|
875
|
+
char* ch;
|
876
|
+
|
877
|
+
TCLinkCon* c = (TCLinkCon*)handle;
|
878
|
+
|
879
|
+
if (name && value) {
|
880
|
+
p = (param*)malloc(sizeof(param));
|
881
|
+
p->name = strdup(name);
|
882
|
+
p->value = strdup(value);
|
883
|
+
p->next = NULL;
|
884
|
+
if (c->send_param_tail)
|
885
|
+
c->send_param_tail->next = p;
|
886
|
+
else
|
887
|
+
c->send_param_list = p;
|
888
|
+
c->send_param_tail = p;
|
889
|
+
|
890
|
+
/* remove newlines and equals signs from the parameter name */
|
891
|
+
for (ch = p->name; *ch; ch++)
|
892
|
+
if (*ch == '=' || *ch == '\n')
|
893
|
+
*ch = ' ';
|
894
|
+
|
895
|
+
/* remove newlines from the value */
|
896
|
+
for (ch = p->value; *ch; ch++)
|
897
|
+
if (*ch == '\n')
|
898
|
+
*ch = ' ';
|
899
|
+
}
|
900
|
+
}
|
901
|
+
|
902
|
+
void
|
903
|
+
TCLinkSend(TCLinkHandle handle)
|
904
|
+
{
|
905
|
+
param *p, *next;
|
906
|
+
char buf[TC_BUFF_MAX], destbuf[TC_LINE_MAX];
|
907
|
+
const int BUF2_LEN = 2048;
|
908
|
+
char buf2[BUF2_LEN];
|
909
|
+
int host_hash = 1;
|
910
|
+
int retval = 0;
|
911
|
+
bool full = false;
|
912
|
+
|
913
|
+
TCLinkCon* c = (TCLinkCon*)handle;
|
914
|
+
|
915
|
+
ClearRecvList(c);
|
916
|
+
|
917
|
+
/* build most of the string we will send to the processor */
|
918
|
+
sprintf(buf, "BEGIN\nversion=%s\n", tclink_version);
|
919
|
+
|
920
|
+
for (p = c->send_param_list; p; p = next) {
|
921
|
+
next = p->next;
|
922
|
+
full = full || SAFE_COPY(buf2, p->name);
|
923
|
+
full = full || SAFE_APPEND(buf2, "=");
|
924
|
+
full = full || SAFE_APPEND(buf2, p->value);
|
925
|
+
full = full || SAFE_APPEND(buf2, "\n");
|
926
|
+
full = full || SAFE_APPEND(buf, buf2);
|
927
|
+
if (!full && !strcasecmp(p->name, "custid")) {
|
928
|
+
|
929
|
+
host_hash = atoi(p->value);
|
930
|
+
host_hash = (host_hash / 100) + (host_hash % 100);
|
931
|
+
}
|
932
|
+
|
933
|
+
free(p->name);
|
934
|
+
free(p->value);
|
935
|
+
free(p);
|
936
|
+
}
|
937
|
+
|
938
|
+
c->send_param_list = c->send_param_tail = NULL;
|
939
|
+
|
940
|
+
/* try to make the connection */
|
941
|
+
if (!full && !Connect(c, host_hash)) {
|
942
|
+
Close(c); /* clean up any memory Connect() may have left lying around */
|
943
|
+
AddRecvParam(c, "status", "error");
|
944
|
+
AddRecvParam(c, "errortype", "cantconnect");
|
945
|
+
return;
|
946
|
+
}
|
947
|
+
|
948
|
+
if (!full) {
|
949
|
+
/* append some data about the connection */
|
950
|
+
snprintf(
|
951
|
+
buf2, BUF2_LEN, "pass=%d\ntime=%ld\n", c->pass, time(0) - c->start_time);
|
952
|
+
full = full || SAFE_APPEND(buf, buf2);
|
953
|
+
if (c->dns != 1)
|
954
|
+
SAFE_APPEND(buf, "dns=n\n");
|
955
|
+
full = full || SAFE_APPEND(buf, "END\n");
|
956
|
+
}
|
957
|
+
|
958
|
+
if (full) {
|
959
|
+
Close(c);
|
960
|
+
AddRecvParam(c, "status", "baddata");
|
961
|
+
AddRecvParam(c, "error", "badlength");
|
962
|
+
AddRecvParam(c, "offenders", "request");
|
963
|
+
return;
|
964
|
+
}
|
965
|
+
|
966
|
+
/* send the data */
|
967
|
+
if (Send(c, buf)) {
|
968
|
+
int state = 0;
|
969
|
+
buf[0] = destbuf[0] = 0; /* recycle buf */
|
970
|
+
c->is_error = 0;
|
971
|
+
while (1) {
|
972
|
+
int len = ReadLine(c, buf, destbuf);
|
973
|
+
if (len == 0)
|
974
|
+
continue;
|
975
|
+
if (len < 0)
|
976
|
+
break;
|
977
|
+
if (strcasecmp(destbuf, "BEGIN") == 0) {
|
978
|
+
if (state != 0) {
|
979
|
+
state = -1;
|
980
|
+
break;
|
981
|
+
}
|
982
|
+
state = 1;
|
983
|
+
} else if (strcasecmp(destbuf, "END") == 0) {
|
984
|
+
state = (state != 1) ? -1 : 2;
|
985
|
+
break;
|
986
|
+
} else {
|
987
|
+
if (state != 1 || !AddRecvString(c, destbuf)) {
|
988
|
+
state = -1;
|
989
|
+
break;
|
990
|
+
}
|
991
|
+
}
|
992
|
+
}
|
993
|
+
if (state == 2)
|
994
|
+
retval = 1;
|
995
|
+
}
|
996
|
+
|
997
|
+
Close(c);
|
998
|
+
|
999
|
+
if (!retval) {
|
1000
|
+
ClearRecvList(c);
|
1001
|
+
AddRecvParam(c, "status", "error");
|
1002
|
+
AddRecvParam(c, "errortype", "linkfailure");
|
1003
|
+
}
|
1004
|
+
}
|
1005
|
+
|
1006
|
+
char*
|
1007
|
+
TCLinkGetResponse(TCLinkHandle handle, const char* name, char* value)
|
1008
|
+
{
|
1009
|
+
param* p;
|
1010
|
+
TCLinkCon* c = (TCLinkCon*)handle;
|
1011
|
+
|
1012
|
+
for (p = c->recv_param_list; p; p = p->next)
|
1013
|
+
if (strcasecmp(name, p->name) == 0) {
|
1014
|
+
safe_copy(value, p->value, PARAM_MAX_LEN);
|
1015
|
+
return value;
|
1016
|
+
}
|
1017
|
+
|
1018
|
+
return NULL;
|
1019
|
+
}
|
1020
|
+
|
1021
|
+
char*
|
1022
|
+
TCLinkGetEntireResponse(TCLinkHandle handle, char* buf, int size)
|
1023
|
+
{
|
1024
|
+
param* p;
|
1025
|
+
int len = 0;
|
1026
|
+
TCLinkCon* c = (TCLinkCon*)handle;
|
1027
|
+
|
1028
|
+
for (p = c->recv_param_list; p; p = p->next) {
|
1029
|
+
stuff_string(buf, &len, size, p->name);
|
1030
|
+
stuff_string(buf, &len, size, "=");
|
1031
|
+
stuff_string(buf, &len, size, p->value);
|
1032
|
+
stuff_string(buf, &len, size, "\n");
|
1033
|
+
}
|
1034
|
+
|
1035
|
+
return buf;
|
1036
|
+
}
|
1037
|
+
|
1038
|
+
void
|
1039
|
+
TCLinkDestroy(TCLinkHandle handle)
|
1040
|
+
{
|
1041
|
+
TCLinkCon* c = (TCLinkCon*)handle;
|
1042
|
+
if (!c)
|
1043
|
+
return;
|
1044
|
+
|
1045
|
+
ClearSendList(c);
|
1046
|
+
ClearRecvList(c);
|
1047
|
+
Close(c);
|
1048
|
+
|
1049
|
+
if (c->ip)
|
1050
|
+
free(c->ip);
|
1051
|
+
|
1052
|
+
if (c->ssl) {
|
1053
|
+
SSL_free(c->ssl);
|
1054
|
+
c->ssl = NULL;
|
1055
|
+
}
|
1056
|
+
|
1057
|
+
if (c->ctx) {
|
1058
|
+
SSL_CTX_free(c->ctx);
|
1059
|
+
c->ctx = NULL;
|
1060
|
+
}
|
1061
|
+
|
1062
|
+
free(c);
|
1063
|
+
}
|
1064
|
+
|
1065
|
+
char*
|
1066
|
+
TCLinkGetVersion(char* buf)
|
1067
|
+
{
|
1068
|
+
strcpy(buf, tclink_version);
|
1069
|
+
return buf;
|
1070
|
+
}
|