new credit card processing module for TrustCommerce

http://trustcommerce.com/tclink.html
This commit is contained in:
Dan Helfman 2002-07-17 22:07:05 +00:00
parent 60ad61def7
commit ee250fb4d6
9 changed files with 3783 additions and 0 deletions

65
ext/tclink/README Normal file
View File

@ -0,0 +1,65 @@
TCLink v3.3
PHP Implementation
copyright (C) TrustCommerce 2002
http://www.trustcommerce.com
developer@trustcommerce.com
July 17, 2002
I. DESCRIPTION
TCLink is a thin client library to allow your e-commerce 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
any questions you may have about the protocol syntax itself.
If you are using Python on Win32, do not use this client. Please
download the COM object from the Vault website instead.
TCLink was originally ported to PHP by Andrew Barnett of Data Clarity.
Thanks go to both him (for the code) and his business (for their support
of open source).
II. COMPILATION
If you would like to recompile PHP with support for TCLink, be sure
to run "./configure --with-openssl" first, as TCLink relies on OpenSSL.
Note that if your PHP installation already includes TCLink, there is
no need to do any compilation.
III. 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/php4) if it is not already installed there by
default. 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:
extension=tclink.so
Restart your webserver, and all PHP scripts will have access to TCLink.
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.
IV. 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.
A note to users of TCLink PHP prior to 3.3: the API has changed from
the C-style interface, to a single tclink_send() function which accepts
a hash (associative array) of input parameters, and returns a hash with
the output parameters. Please see the examples to update your code.

6
ext/tclink/config.m4 Normal file
View File

@ -0,0 +1,6 @@
if test "$PHP_TCLINK" != "no"; then
PHP_NEW_EXTENSION(tclink, tclink.c, $ext_shared)
TCLINK_SHARED_LIBADD="-lcrypto -lssl"
PHP_SUBST(TCLINK_SHARED_LIBADD)
AC_DEFINE(HAVE_TCLINK_EXT,1,[ ])
fi

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

123
ext/tclink/php_tclink.c Normal file
View File

@ -0,0 +1,123 @@
/*
* TCLink PHP Module
*
* TCLink Copyright (c) 2002 TrustCommerce.
* http://www.trustcommerce.com
* developer@trustcommerce.com
* (626) 744-7700
*/
#include "php.h"
#include "php_config.h"
#include "php_tclink.h"
function_entry php_tclink_functions[] = {
PHP_FE(tclink_send, NULL)
PHP_FE(tclink_getversion, NULL)
{NULL, NULL, NULL}
};
zend_module_entry php_tclink_module_entry = {
#ifdef STANDARD_MODULE_HEADER
STANDARD_MODULE_HEADER,
#endif
"tclink", php_tclink_functions, NULL, NULL, NULL, NULL, PHP_MINFO(tclink), STANDARD_MODULE_PROPERTIES
};
#define PHP_TCLINK_DEFAULT_BUFFER_SIZE 8196
#ifdef COMPILE_DL_TCLINK
ZEND_GET_MODULE(php_tclink)
PHP_MINFO_FUNCTION(tclink)
{
char *tmp = (char *)malloc(1024);
php_info_print_table_start();
if(tmp == NULL) {
php_info_print_table_row(2, "TCLink PHP Module", "enabled");
} else {
php_info_print_table_row(2, "TCLink PHP Module", TCLinkGetVersion(tmp));
free(tmp);
}
php_info_print_table_end();
}
/* {{{ proto void tclink_send(array params)
Send the transaction in for processing. */
PHP_FUNCTION(tclink_send)
{
pval **params, **zvalue;
HashTable *hash;
char *key, *value, *next_key;
TCLinkHandle h;
char buf[4096];
/* check parameters */
if((ZEND_NUM_ARGS() != 1) ||
(zend_get_parameters_ex(1, &params) == FAILURE)) {
WRONG_PARAM_COUNT;
}
convert_to_array_ex(params);
h = TCLinkCreate();
/* grab the hash and stuff each parameter set into TCLink */
hash = HASH_OF(*params);
zend_hash_internal_pointer_reset(hash);
while (zend_hash_get_current_data(hash, (void **)&zvalue) == SUCCESS)
{
/* The Zend API added an extra parameter between 4.04 (sometime in
* 1999) and 4.06 (in early 2001). Assume that anything prior to
* 1/1/2001 is the older version. */
#if PHP_API_VERSION < 20000101
zend_hash_get_current_key(hash, &key, NULL);
#else
zend_hash_get_current_key(hash, &key, NULL, 0);
#endif
convert_to_string_ex(zvalue);
value = Z_STRVAL_PP(zvalue);
TCLinkPushParam(h, key, value);
zend_hash_move_forward(hash);
}
/* send the transaction */
TCLinkSend(h);
/* pull out the parameters and put them in a hash */
TCLinkGetEntireResponse(h, buf, sizeof(buf));
array_init(return_value);
key = value = buf;
while (key && (value = strchr(key, '=')))
{
*value++ = 0;
next_key = strchr(value, '\n');
if (next_key) *next_key++ = 0;
add_assoc_string(return_value, key, value, 1);
key = next_key;
}
TCLinkDestroy(h);
/* return_value is returned automatically, we need not explictly call a
return macro */
}
/* }}} */
/* {{{ proto string tclink_getversion()
returns the API version information */
PHP_FUNCTION(tclink_getversion)
{
char version[1024];
TCLinkGetVersion(version);
RETURN_STRING(version, 1);
}
/* }}} */
#endif

68
ext/tclink/php_tclink.h Normal file
View File

@ -0,0 +1,68 @@
/*
* TCLink PHP Module
*
* TCLink Copyright (c) 2002 TrustCommerce.
* http://www.trustcommerce.com
* developer@trustcommerce.com
* (626) 744-7700
*/
#ifndef PHP_TCLINK_H
#define PHP_TCLINK_H
/* Handle passed to all TCLink functions. A unique handle must be created
* 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 *
/* Parameter names and values cannot exceed this size.
*/
#define PARAM_MAX_LEN 256
/* Create a new TCLinkHandle.
*/
TCLinkHandle TCLinkCreate();
/* Add a parameter to be sent to the server.
*/
void TCLinkPushParam(TCLinkHandle handle, const char *name, const char *value);
/* Flush the parameters to the server.
*/
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);
/* 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);
/* 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);
#if HAVE_TCLINK
extern zend_module_entry php_tclink_module_entry;
#define tclink_module_ptr &php_tclink_module_entry
PHP_MINFO_FUNCTION(tclink);
PHP_FUNCTION(tclink_send);
PHP_FUNCTION(tclink_getversion);
#else
#define tclink_module_ptr NULL
#endif
#define phpext_tclink_ptr tclink_module_ptr
#endif /* PHP_TCLINK_H */

101
ext/tclink/tcexample.php Normal file
View File

@ -0,0 +1,101 @@
<?
// This example assumes that tclink.so has already been loaded.
// Normally this is done by adding "extension=tclink.so" to your
// php.ini, but you may also make a manual call to dl("tclink")
// as well. See tctest.php for example code.
if (!$custid) $custid = "TestMerchant";
if (!$password) $password = "password";
?>
<html>
<head><title>TCLink PHP Example</title></head>
<body bgcolor=white text=black>
<form method="post" action="<?= $PHP_SELF ?>">
<table cellspacing=1 cellpadding=3>
<tr bgcolor=blue><th colspan=2 align=center>
<font color=white>TrustCommerce PHP Example - TCLink ver. <?= tclink_getversion() ?>)</font>
</th></tr>
<tr><th align=right> CustID: </td><td> <input type="text" name="custid" value="<?= $custid ?>"> </td></tr>
<tr><th align=right> Password: </td><td> <input type="text" name="password" value="<?= $password ?>"> </td></tr>
<tr><th align=right> Action: </td><td> <select name="action">
<option value="sale">Sale</option>
<option value="preauth">Pre-Authorization</option>
<option value="postauth">Post-Authorization</option>
<option value="credit">Credit</option>
</select> </td></tr>
<tr><th align=right> Amount (in cents):</td><td> <input type="text" name="amount"> </td></tr>
<tr bgcolor=lightgray><td colspan=2 align=center> Sales and Pre-Authorizations Only: </td></tr>
<tr><th align=right> Card Number: </td><td> <input type="text" name="cc" size="16" maxlength="16"> </td></tr>
<tr><th align=right> Expiration: </td>
<td><select name="mm"><? for ($i = 1; $i <= 12; $i++) { ?><option value="<?=sprintf("%02d", $i);?>"><?=sprintf("%02d", $i);?></option><? } ?></select>
<select name="yy"><? for($i = (strftime("%Y")); $i <= (strftime("%Y") + 10); $i++) { ?><option value="<?=substr(sprintf("%04d", $i),2,2);?>"><?=$i;?></option><? } ?></select><br>
</td></tr>
<tr><th align=right> Cardholder Name: </td><td> <input type="text" name="name"> </td></tr>
<tr bgcolor=lightgray><td colspan=2 align=center> Credits and Post-Authorizations Only: </td></tr>
<tr><th align=right> Transaction ID: </td><td> <input type="text" name="transid" size="14" maxlength="14"> </td></tr>
<tr><td colspan=2 align=center> <input type="submit" name="Action" value="Process"> </td></tr>
<?
if ($Action == 'Process')
{
$tclink['custid'] = $custid;
$tclink['password'] = $password;
$tclink['action'] = $action;
if (is_numeric($amount))
$tclink['amount'] = $amount;
if ($action == 'sale' || $action == 'preauth')
{
$tclink['name'] = $name;
$tclink['cc'] = $cc;
$tclink['exp'] = $mm . $yy;
}
else if ($action == 'credit' || $action == 'postauth')
{
$tclink['transid'] = $transid;
}
$result = tclink_send($tclink);
print "<tr><td colspan=2><hr></td></tr>";
print "<tr bgcolor=blue><th colspan=2 align=center><font color=white>Transaction Results:</font></td></tr>";
if ($result['transid'])
printf("<tr><th>Transaction ID:</th><td>%s</td></tr>\n", $result['transid']);
printf("<tr><th>Status:</td><td>%s</td></tr>\n", $tclink['status']);
switch($tclink['status'])
{
case 'accepted':
case 'approved':
break;
case 'decline':
case 'rejected':
printf("<tr><th>Decline Type:</th><td>%s</td></tr>\n", $tclink['declinetype']);
break;
case 'error':
printf("<tr><th>Error Type</th><td>%s</td></tr>\n", $tclink['errortype']);
break;
case 'baddata':
printf("<tr><th>Offenders:</th><td>%s</td></tr>\n", $tclink['$offenders']);
break;
}
print "<tr bgcolor=lightgray><td colspan=2 align=center>All Results:</td></tr>";
while(list($key, $value) = each($result))
printf("<tr><th>%s</th><td>%s</td></tr>\n", $key, $value);
}
?>
</table>
</form>
</body>
</html>

877
ext/tclink/tclink.c Normal file
View File

@ -0,0 +1,877 @@
/* tclink.c - Library code for the TCLink client API.
*
* TCLink Copyright (c) 2002 TrustCommerce.
* http://www.trustcommerce.com
* developer@trustcommerce.com
* (626) 744-7700
*/
#include "php_tclink.h"
#include <stdio.h>
#include <memory.h>
#include <errno.h>
#include <string.h>
#include <strings.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/poll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#define DEFAULT_HOST "gateway.trustcommerce.com"
#define TIMEOUT 40 /* seconds */
#define TC_BUFF_MAX 16000
#define TC_LINE_MAX ((PARAM_MAX_LEN * 2) + 2)
char *tclink_version = "3.3-PHP";
char *tclink_host = DEFAULT_HOST;
int tclink_port = 443;
/*************************************************/
/* Data structures used only within this module. */
/*************************************************/
/* Variables used for transaction data. */
typedef struct param_data
{
char *name;
char *value;
struct param_data *next;
} param;
typedef struct _TCLinkCon
{
/* Connection data */
int *ip;
int num_ips;
int sd;
/* SSL encryption */
X509 *tc_cert;
SSL_METHOD *meth;
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;
} TCLinkCon;
/* The TrustCommerce certificate. */
unsigned char cert_data[540]={
0x30,0x82,0x02,0x18,0x30,0x82,0x01,0x81,0x02,0x01,0x02,0x30,0x0D,0x06,0x09,0x2A,
0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x30,0x55,0x31,0x0B,0x30,0x09,
0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x14,0x30,0x12,0x06,0x03,0x55,
0x04,0x08,0x13,0x0B,0x4C,0x6F,0x73,0x20,0x41,0x6E,0x67,0x65,0x6C,0x65,0x73,0x31,
0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,0x0E,0x54,0x72,0x75,0x73,0x74,0x20,
0x43,0x6F,0x6D,0x6D,0x65,0x72,0x63,0x65,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,
0x03,0x13,0x0E,0x50,0x43,0x41,0x20,0x28,0x31,0x30,0x32,0x34,0x20,0x62,0x69,0x74,
0x29,0x30,0x1E,0x17,0x0D,0x30,0x30,0x30,0x34,0x32,0x39,0x30,0x35,0x30,0x39,0x30,
0x34,0x5A,0x17,0x0D,0x30,0x34,0x30,0x34,0x32,0x39,0x30,0x35,0x30,0x39,0x30,0x34,
0x5A,0x30,0x54,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,
0x31,0x14,0x30,0x12,0x06,0x03,0x55,0x04,0x08,0x13,0x0B,0x4C,0x6F,0x73,0x20,0x41,
0x6E,0x67,0x65,0x6C,0x65,0x73,0x31,0x17,0x30,0x15,0x06,0x03,0x55,0x04,0x0A,0x13,
0x0E,0x54,0x72,0x75,0x73,0x74,0x20,0x43,0x6F,0x6D,0x6D,0x65,0x72,0x63,0x65,0x31,
0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x03,0x13,0x0D,0x43,0x41,0x20,0x28,0x31,0x30,
0x32,0x34,0x20,0x62,0x69,0x74,0x29,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,
0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,
0x02,0x81,0x81,0x00,0xBD,0x7C,0x7B,0x6F,0x77,0x46,0xE3,0x0F,0xF8,0x50,0x89,0x06,
0xFC,0x54,0x5A,0x59,0x30,0x55,0xC6,0x00,0x34,0x6F,0x6B,0x64,0x8E,0x11,0x3C,0xDD,
0xA9,0x0D,0xC5,0xE1,0x1C,0x49,0xF7,0x0A,0x6B,0x3E,0xAA,0x98,0xA4,0xA2,0x8D,0xEF,
0x9A,0xCB,0xA2,0x40,0x87,0x3B,0x4B,0x13,0x73,0xE6,0x6C,0x39,0x1C,0x48,0xBE,0x6C,
0x1C,0x78,0x0F,0x8E,0x40,0x27,0xAD,0x61,0x0E,0x5E,0x1F,0x94,0xD7,0xAB,0x61,0x3C,
0xB1,0xF4,0xC9,0xE2,0x0D,0x05,0x83,0xE8,0x75,0xAB,0x64,0x12,0x39,0xAB,0xEF,0x79,
0x53,0x49,0x48,0xA0,0x9C,0x55,0xD2,0xE3,0xD0,0x25,0x94,0x78,0x69,0x03,0x95,0xBA,
0x68,0xC6,0x35,0xFB,0x54,0x3D,0x05,0x6D,0xAD,0x50,0x5F,0xE7,0x63,0xB9,0x4A,0x28,
0xB0,0xB3,0xE2,0x07,0x02,0x03,0x01,0x00,0x01,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,
0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x03,0x81,0x81,0x00,0x34,0xED,0xF6,0x9D,
0x32,0x51,0xCA,0x22,0xD6,0x8C,0x81,0x6F,0x64,0x41,0x7D,0xC0,0xD8,0x66,0xB7,0x2C,
0x65,0x54,0x8A,0xE4,0x21,0x6A,0xF2,0x0F,0x3F,0xD0,0x85,0xDC,0x15,0xC1,0x5C,0x72,
0x9A,0x0F,0x00,0x8D,0x38,0x59,0x70,0x7A,0x9C,0x40,0xA6,0x9A,0x2D,0x0E,0xA0,0x31,
0x61,0x2E,0xA2,0x77,0x11,0xBB,0x20,0xF8,0xF9,0x28,0x10,0x1E,0x12,0xB1,0x9D,0x29,
0xF7,0x86,0x12,0x05,0x83,0x83,0xE3,0xC3,0x82,0x65,0x97,0xE9,0xC2,0x5B,0x09,0x11,
0x1D,0xF1,0x01,0x37,0x20,0x2E,0xC5,0x69,0x9C,0xED,0xE3,0xC1,0x29,0x1B,0x3D,0x47,
0x72,0xED,0xA1,0x7B,0xE4,0x8B,0x2B,0x18,0x39,0xEA,0xDE,0x54,0x69,0xE7,0x35,0xDB,
0x8F,0xFB,0x34,0xC7,0xF7,0xB3,0x6A,0x9A,0xE5,0x27,0xA4,0x0F};
/*************************************
* Internal functions, not exported. *
*************************************/
/* Random number from min to max. */
static int number(int min, int max)
{
return (rand() % (max - min + 1)) + min;
}
/* 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));
void safe_copy(char *dst, const char *src, int size)
{
int len = strlen(src);
if (len < size)
strcpy(dst, src);
else {
strncpy(dst, src, size - 1);
dst[size-1] = 0;
}
}
void 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;
if (slen < avail)
strcpy(dst+dlen, src);
else {
strncpy(dst+dlen, src, avail - 1);
dst[size-1] = 0;
}
}
/* Add a parameter-value pair to the recieved list. */
static void AddRecvParam(TCLinkCon *c, const char *name, const char *value)
{
param *p;
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;
}
/* Add a string to the received list. */
static int AddRecvString(TCLinkCon *c, char *string)
{
char *ptr = strchr(string, '=');
if (ptr == NULL)
return 0;
*ptr = 0;
AddRecvParam(c, string, ptr+1);
return 1;
}
/* Deallocate the send list. */
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;
}
/* Deallocate the recv list. */
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;
}
/* 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.
*/
static int BeginConnection(TCLinkCon *c, int host_ip)
{
struct sockaddr_in sa;
int sd;
sd = socket(AF_INET, SOCK_STREAM, 0);
if (sd < 0)
return -1;
fcntl(sd, F_SETFL, O_NONBLOCK);
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));
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.
*/
static int FinishConnection(TCLinkCon *c, int sd)
{
int ssl_connected, is_error, errcode, res, n;
struct pollfd pfd;
X509 *server_cert;
time_t start, remaining;
/* check if socket has connected successfully */
int val;
int /*socklen_t*/ size = 4;
getsockopt(sd, SOL_SOCKET, SO_ERROR, &val, &size);
if (val != 0)
return 0;
c->ssl = SSL_new(c->ctx);
if (!c->ssl)
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)
{
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:
case SSL_ERROR_WANT_WRITE:
/* no error, just wait for more data */
pfd.fd = sd; pfd.events = POLLOUT|POLLIN; pfd.revents = 0;
while ((n = poll(&pfd, 1, remaining * 1000)) < 0)
if (errno != EINTR) {
is_error = 1;
break;
}
if (!n || !(pfd.revents & (POLLOUT|POLLIN)))
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) {
SSL_free(c->ssl);
return 0;
}
fcntl(sd, F_SETFL, 0); /* make the socket blocking again */
/* verify that server certificate is authentic */
server_cert = SSL_get_peer_certificate(c->ssl);
if (!server_cert || (X509_cmp(server_cert, c->tc_cert) != 0)) {
SSL_free(c->ssl);
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.
*/
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;
}
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);
}
}
/* Open a connection to one of the TrustCommerce gateway servers. */
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;
unsigned char *cert_data_ptr = cert_data;
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("204.212.133.5");
addr[1] = inet_addr("216.34.194.254");
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 = SSLv3_client_method();
}
if (!c->ctx)
{
c->ctx = SSL_CTX_new(c->meth);
if (!c->ctx) return 0;
}
/* create the valid certificate */
if (c->tc_cert == NULL) {
c->tc_cert = d2i_X509(NULL, &cert_data_ptr, 540);
if (!c->tc_cert) 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;
}
}
}
return 0;
}
/* 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;
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.
*/
static int ReadLine(TCLinkCon *c, char *buffer, char *destbuf)
{
struct timeval tv;
fd_set read;
fd_set error;
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;
if (select(c->sd + 1, &read, NULL, &error, &tv) < 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)
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.
*/
static int Close(TCLinkCon *c)
{
if (c->ssl) SSL_shutdown(c->ssl);
if (c->sd >= 0) {
close(c->sd);
c->sd = -1;
}
if (c->ssl) {
SSL_free(c->ssl);
c->ssl = NULL;
}
if (c->ctx) {
SSL_CTX_free(c->ctx);
c->ctx = NULL;
}
/* We DON'T free c->meth or c->tc_cert here, because they can be *
* reused by other transactions run on this same TCLinkHandle. */
return 1;
}
/**********************************************
* API functions exported to the user client. *
**********************************************/
TCLinkHandle TCLinkCreate()
{
TCLinkCon *c = (TCLinkCon *)malloc(sizeof(TCLinkCon));
c->ip = NULL;
c->num_ips = 0;
c->sd = -1;
c->tc_cert = NULL;
c->meth = NULL;
c->ctx = NULL;
c->ssl = 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;
return (TCLinkHandle)c;
}
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 = ' ';
}
}
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)
{
if (state != 1)
state = -1;
else
state = 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)
{
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;
}
return NULL;
}
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;
}
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;
}
void TCLinkDestroy(TCLinkHandle handle)
{
TCLinkCon *c = (TCLinkCon *)handle;
if (!c) return;
ClearSendList(c);
ClearRecvList(c);
Close(c);
if (c->ip)
free(c->ip);
if (c->tc_cert)
X509_free(c->tc_cert);
free(c);
}
char *TCLinkGetVersion(char *buf)
{
strcpy(buf, tclink_version);
return buf;
}

45
ext/tclink/tctest.php Normal file
View File

@ -0,0 +1,45 @@
<?
// The ugly bit of code below loads the TCLink extension if it is not
// already. You can skip all of this if "extension=tclink.so" is in
// your php.ini, or you can make a single call to dl("tclink") if
// tclink.so is in your PHP extensions path ("extension_dir").
if (!extension_loaded("tclink"))
{
$extfname = getcwd() . "/modules/tclink.so";
echo "TCLink extension not loaded, attempting to load manually from $extfname...";
// The excessive "../" bit allows us to back up out of the extension dir
// so that we can access the current working directory. It is only done
// this way so that tctest.php may work out of the box on any system, you
// can get rid of this if you do a global install of TCLink.
$extfname = "../../../../../../../../../../../.." . $extfname;
if (!dl($extfname)) {
echo "FAILED!\nAborting this test script, please check to make sure the module is properly built.\n";
exit(1);
}
echo "success.\n";
}
print "\nTCLink version " . tclink_getversion() . "\n";
print "Sending transaction...";
// Build a hash containing our parameters
$params{'custid'} = 'TestMerchant';
$params{'password'} = 'password';
$params{'action'} = 'sale';
$params{'media'} = 'cc';
$params{'cc'} = '4111111111111111';
$params{'exp'} = '0110';
$params{'amount'} = '100';
$params{'name'} = 'Joe PHP';
// Send the hash to TrustCommerce for processing
$result = tclink_send($params);
print "done!\n\nTransaction results:\n";
// Print out all parameters returned
while (list($key, $val) = each($result))
print "\t$key=$val\n";
?>