<?php
/* 
 **************************************************************************
 *
 * OpenSRS-PHP
 *
 * Copyright (C) 2000, 2001, 2002, 2003 Colin Viebrock
 *   and easyDNS Technologies Inc.
 *
 * Version 2.7.3
 *   15-Aug-2003
 *
 **************************************************************************
 *
 * 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
 *
 **************************************************************************
 *
 * vim: set expandtab tabstop=4 shiftwidth=4:
 * $Id: openSRS_base.php,v 1.4 2005/06/29 23:01:22 thomas Exp $
 *
 **************************************************************************
 */


# assuming that the PEAR directory is within your path, otherwise change the path here

require_once 'PEAR.php';
require_once 'Crypt/CBC.php';


# local requirements

require_once "$dtcshared_path/dtcrm/srs/OPS.php";
require_once "$dtcshared_path/dtcrm/srs/country_codes.php";



class openSRS_base extends PEAR {


	var $USERNAME				= '';
	var $HRS_USERNAME			= '';

	var $PRIVATE_KEY			= '';
	var $TEST_PRIVATE_KEY		= '';
	var $LIVE_PRIVATE_KEY		= '';
	var $HRS_PRIVATE_KEY		= '';

	var $VERSION				= 'XML:0.1';

	var $base_class_version		= '2.7.3';

	var $environment			= 'TEST';	/* 'TEST' or 'LIVE' or 'HRS' */
	var $protocol				= 'XCP';	/* 'XCP' or 'TPP' */

	var $LIVE_host				= 'rr-n1-tor.opensrs.net';
	var $LIVE_port		 		= 55000;
	var $TEST_host				= 'horizon.opensrs.net';
	var $TEST_port				= 55000;
	var $HRS_host;
	var $HRS_port;
	var $REMOTE_HOST;
	var $REMOTE_PORT;

	var $connect_timeout		= 20;				/* seconds */
	var $read_timeout			= 20;				/* seconds */

	var $log					= array();

	var $_socket				= false;
	var $_socket_error_num		= false;
	var $_socket_error_msg		= false;
	var $_session_key			= false;

	var $_OPS;
	var $_CBC;

	var $lookup_all_tlds		= false;

	var $_CRYPT;
	var $_iv;
	var $crypt_type				= 'DES';				/* 'DES' or 'BLOWFISH' */
	var $crypt_mode				= 'CBC';				/* only 'CBC' */
	var $crypt_rand_source		= MCRYPT_DEV_URANDOM;	/* or MCRYPT_DEV_RANDOM or MCRYPT_RAND */
	var $affiliate_id;

	var $PERMISSIONS = array (
		'f_modify_owner'	=> 1,
		'f_modify_admin'	=> 2,
		'f_modify_billing'	=> 4,
		'f_modify_tech'		=> 8,
		'f_modify_nameservers'	=> 16
	);

	var $REG_PERIODS = array (
		1	=> '1 Year',
		2	=> '2 Years',
		3	=> '3 Years',
		4	=> '4 Years',
		5	=> '5 Years',
		6	=> '6 Years',
		7	=> '7 Years',
		8	=> '8 Years',
		9	=> '9 Years',
		10	=> '10 Years'
	);

	var $UK_REG_PERIODS = array (
		2	=> '2 Years'
	);

	var $TV_REG_PERIODS = array (
		1	=> '1 Year',
		2	=> '2 Years',
		3	=> '3 Years',
		5	=> '5 Years',
		10	=> '10 Years'
	);

	var $TRANSFER_PERIODS = array (
		1	=> '1 Year'
	);

	var $OPENSRS_TLDS_REGEX = '(\.ca|\.(bc|ab|sk|mb|on|qc|nb|ns|pe|nf|nt|nv|yk)\.ca|\.com|\.net|\.org|\.co\.uk|\.org\.uk|\.tv|\.vc|\.cc|\.info|\.biz|\.name|\.us)';

	var $CA_LEGAL_TYPES = array (
		'ABO'	=> 'Aboriginal',
		'ASS'	=> 'Association',
		'CCO'	=> 'Canadian Corporation', 
		'CCT'	=> 'Canadian Citizen',
		'EDU'	=> 'Educational Institute',
		'GOV'	=> 'Government',
		'HOP'	=> 'Hospital',
		'INB'	=> 'Indian Band',
		'LAM'	=> 'Library, Archive or Museum',
		'LGR'	=> 'Legal Respresentative',
		'MAJ'	=> 'Her Majesty the Queen',
		'OMK'	=> 'Protected by Trade-marks Act',
		'PLT'	=> 'Political Party',
		'PRT'	=> 'Partnership',
		'RES'	=> 'Permanent Resident',
		'TDM'	=> 'Trade-mark Owner',
		'TRD'	=> 'Trade Union',
		'TRS'	=> 'Trust'
	);

	var $CA_LANGUAGE_TYPES = array (
		'EN'	=> 'English',
		'FR'	=> 'French'
	);

	var $CA_NATIONALITIES = array (
		'CND'	=> 'Canadian citizen',
		'OTH'	=> 'Foreign citizenship',
		'RES'	=> 'Canadian permanent resident'
	);


	var $OPENSRS_ACTIONS = array (
		'get_domain'				=> true,
		'get_userinfo'				=> true,

		'modify_domain'				=> true,
		'renew_domain'				=> true,
		'register_domain'			=> true,

		'get_nameserver'			=> true,
		'create_nameserver'			=> true,
		'modify_nameserver'			=> true,
		'delete_nameserver'			=> true,

		'get_subuser'				=> true,
		'add_subuser'				=> true,
		'modify_subuser'			=> true,
		'delete_subuser'			=> true,

		'change_password'			=> true,
		'change_ownership'			=> true,

		'set_cookie'				=> true,
		'delete_cookie'				=> true,
		'update_cookie'				=> true,

		'sw_register_domain'		=> true,
		'bulk_transfer_domain'		=> true,
		'register_domain'			=> true,

		'lookup_domain'				=> true,
		'get_price_domain'			=> true,

		'check_transfer_domain'		=> true,
		'quit_session'				=> true,

		'buy_webcert'				=> true,
		'refund_webcert'			=> true,	
		'query_webcert'				=> true,
		'cprefget_webcert'			=> true,
		'cprefset_webcert'			=> true,
		'cancel_pending_webcert'	=> true,
		'update_webcert'			=> true,

	);



#
#	BASIC PUBLIC FUNCTIONS
#


	/**
	 * Class constructor
	 *
	 * Initialize variables, logs, etc.
	 *
	 * @param	string		Which environment to use (LIVE or TEST or HRS)
	 * @param	string		Which protocol to use (XCP or TPP)
	 *
	 */

	function openSRS_base( $environment=NULL, $protocol=NULL )
	{

		$this->PEAR();

		$this->_log('i', 'OpenSRS Log:');
		$this->_log('i', 'Initialized: '.date('r') );

		$this->_OPS	= new OPS;

		if ($environment) {
			$this->environment = strtoupper($environment);
		}
		if ($protocol) {
			$this->protocol = strtoupper($protocol);
		}

		$this->_log('i', 'Environment: '.$this->environment );
		$this->_log('i', 'Protocol: '.$this->protocol );

		$this->PRIVATE_KEY = $this->{$this->environment.'_PRIVATE_KEY'};

		$this->_CBC	= false;

	}


#
#	setProtocol()
#		Switch between XCP and TPP
#


	function setProtocol( $proto )
	{

		$proto = trim(strtoupper($proto));

		switch ($proto) {
		  case 'XCP':
		  case 'TPP':
			$this->protocol = $proto;

			$this->_log('i', 'Set protocol: '.$this->protocol );

			return true;
			break;

		  default:
			return array(
				'is_success'	=> false,
				'error'			=> 'Invalid protocol: ' . $proto
			);
			break;
		}

	}


#
#	logout()
#		Send a 'quit' command to the server
#

	function logout() {
		if ($this->_socket) {
			$this->send_cmd( array(
				'action'	=> 'quit',
				'object'	=> 'session' )
			);
			$this->close_socket();
		}
	}


#
#	send_cmd()
#		Send a command to the server
#

	function send_cmd($request) {
		global $HTTP_SERVER_VARS;

		if (!is_array($request)) {
			$data = array(
				'is_success'	=> false,
				'response_code'	=> 400,
				'response_text'	=> 'Invalid command (not an array): '.$request
			);
			$this->_log('i',$data);
			return $data;
		}

		$action = $request['action'];
		$object = $request['object'];

		# prune any private data before sending down to the server
		# (eg. credit card numbers and information

		$this->prune_private_keys($request);

#
# Disable action checking.  This means you don't need to update the code
# each time OpenSRS adds a new command, but means you should be more 
# careful with your coding ...
#
#		if (!isset($this->OPENSRS_ACTIONS[$action.'_'.$object])) {
#			$data = array(
#				'is_success'	=> false,
#		        	'response_code'	=> 400,
#				'response_text'	=> 'Invalid command: '.$action.' '.$object
#			);
#			$this->_log('i',$data);
#			return $data;
#		}


		# make or get the socket filehandle

		if (!$this->init_socket() ) {
			$data = array(
				'is_success'	=> false,
				'response_code'	=> 400,
				'response_text'	=> 'Unable to establish socket: (' .
					$this->_socket_err_num . ') ' . $this->_socket_err_msg
			);
			$this->_log('i',$data);
			return $data;
		}

		if ($this->environment == 'HRS') {
			$auth = $this->authenticate($this->HRS_USERNAME,$this->PRIVATE_KEY);
		} else {
			$auth = $this->authenticate($this->USERNAME,$this->PRIVATE_KEY);
		}

		if (!$auth['is_success']) {
			if ($this->_socket) {
				$this->close_socket();
			}
			$data = array(
				'is_success'	=> false,
				'response_code'	=> 400,
				'response_text'	=> 'Authentication Error: ' . $auth['error']
			);
			$this->_log('i',$data);
			return $data;
		}

		$request['registrant_ip'] = $HTTP_SERVER_VARS['REMOTE_ADDR'];

		if ( strstr($request['action'], 'lookup') ) {
			# lookups are treated specially 
			$data = $this->lookup_domain( $request );
		} else {
			# send request to server
			$this->send_data( $request );
			$data = $this->read_data( );
		}

		return $data;

	}


#
#	validate()
#		Check data for validity
#

	function validate($data,$params=array()) {

		# Country codes are needed for checking ... more reliable than /^[A-Z]{2}$/
		include 'country_codes.php';

		$missing_fields = $problem_fields = array();

		$required_contact_fields = array (
			'first_name'	=> 'First Name',
			'last_name'	=> 'Last Name',
			'org_name'	=> 'Organization Name',
			'address1'	=> 'Address1',
			'city'		=> 'City',
			'country'	=> 'Country',
			'phone'		=> 'Phone',
			'email'		=> 'E-Mail'
			);

		$contact_types = array (
			'owner'		=> '',
			'billing'	=> 'Billing'
			);

		$required_fields = array (
			'reg_username'	=> 'Username',
			'reg_password'	=> 'Password',
			'domain'	=> 'Domain',
			);

		if (isset($params['custom_tech_contact'])) {
			$contact_types['tech'] = 'Tech';
		}

		# The primary and secondary nameservers are required.

		if ( isset($params['custom_nameservers']) && $data['reg_type']=='new' )	{
			if (!$data['fqdn1']) {
				$missing_fields[] = 'Primary DNS Hostname';
			}
			if (!$data['fqdn2']) {
				$missing_fields[] = 'Secondary DNS Hostname';
			}
		}

		# check the required fields

		foreach ($contact_types as $type=>$contact_type) {

			foreach ($required_contact_fields as $field=>$required_field) {
				$data[$type.'_'.$field] = trim($data[$type.'_'.$field]);

				if ($data[$type.'_'.$field] == '') {
					$missing_fields[] = $contact_type.' '.$required_field;
				}
			}

			$data[$type.'_country'] = strtolower($data[$type.'_country']);

			if ($data[$type.'_country']=='us' || $data[$type.'_country']=='ca') {
				if ($data[$type.'_postal_code']=='') {
					$missing_fields[] = $contact_type.' Zip/Postal Code';
				}
				if ($data[$type.'_state']=='') {
					$missing_fields[] = $contact_type.' State/Province';
				}
			}

			if (!isset($COUNTRY_CODES[$data[$type.'_country']])) {
				$problem_fields[$contact_type.' Country'] = $data[$type.'_country'];
			}

			if (!$this->check_email_syntax($data[$type.'_email'])) {
				$problem_fields[$contact_type.' Email'] = $data[$type.'_email'];
			}

			if (!preg_match('/^\+?[\d\s\-\.\(\)]+$/', $data[$type.'_phone'] )) {
				$problem_fields[$contact_type.' Phone'] = $data[$type.'_phone'];
			}
		}

		foreach ($required_fields as $field=>$required_field) {
			if ($data[$field] == '') {
				$missing_fields[] = $required_field;
			}
		}

		# these fields must have at least an alpha in them

		foreach ($data as $field=>$value) {
			if ($value=='') {
				continue;		# skip blanks
			}

			if ($field=='first_name' || $field=='last_name' || $field=='org_name' || $field=='city' || $field=='state') {
				if (!preg_match('/[a-zA-Z]/', $value)) {
					$error_msg .= "Field $field must contain at least 1 alpha character.<br>\n";
				}
			}
		}

		# take $missing_fields and add them to $error_msg

		foreach ($missing_fields as $field) {
			$error_msg .= "Missing field: $field.<br>\n";
		}


		# check syntax on several fields
		# check domain, country, billing_country, email, billing_email, phone,
		# and billing_phone

		$domains = explode("\0", $data['domain'] );
		foreach ($domains as $domain) {
			$syntaxError = $this->check_domain_syntax($domain);
			if ($syntaxError) {
				$problem_fields['Domain'] = $domain . " - " . $syntaxError;
			}
		}


		# print error if $problem_fields

		if (count(array_keys($problem_fields))) {
			foreach ($problem_fields as $field=>$problem) {
				# only show problem fields if it had a value.  Otherwise, it
				# would have been caught above.
				if ($problem != '') {
					$error_msg .= "The field \"$field\" contained invalid characters: <i>$problem</i><br>\n";
				}
			}
		}


		# insert other error checking here...

		if ($error_msg) {
			return ( array('error_msg' => $error_msg) );
		} else {
			return ( array('is_success' => true) );
		}
	}


#
#	version()
#		return base class version
#

	function version() {
		return 'OpenSRS-PHP Class version '.$this->base_class_version;
	}




#
#	PRIVATE FUNCTIONS
#

#
#	init_socket()
#		Initialize a socket connection to the OpenSRS server
#

	function init_socket() {

		if ($this->_socket) {
			return true;
		}

		if (!$this->environment) {
			return false;
		}

		$this->REMOTE_HOST = $this->{$this->environment.'_host'};
		$this->REMOTE_PORT = $this->{$this->environment.'_port'};

		# create a socket

		$this->_socket = fsockopen($this->REMOTE_HOST, $this->REMOTE_PORT,
			$this->_socket_err_num, $this->_socket_err_msg, $this->connect_timeout );

		if (!$this->_socket) {
			echo "Socket not created!";
			return false;
		} else {
			$this->_log('i','Socket initialized: ' . $this->REMOTE_HOST . ':' . $this->REMOTE_PORT );
			return true;
		}
	}


#
#	authenticate()
#		Authenticate the connection with the username/private key
#

	function authenticate($username=false,$private_key=false) {

		if (isset($this->_authenticated) && $this->_authenticated) {
			return array('is_success' => true);
		}

		if (!$username) {
			return array(
				'is_success'	=> false,
				'error'			=> 'Missing reseller username'
			);
		} else if (!$private_key) {
			return array(
				'is_success'	=> false,
				'error'			=> 'Missing private key'
			);
		}

		$prompt = $this->read_data();

		if ( isset($prompt['response_code']) && $prompt['response_code'] == 555 ) {
			# the ip address from which we are connecting is not accepted
			return array(
				'is_success'	=> false,
				'error'			=> $prompt['response_text']
			);
		} else if ( !preg_match('/OpenSRS\sSERVER/', $prompt['attributes']['sender']) ||
			substr($prompt['attributes']['version'],0,3) != 'XML' ) {
			return array(
				'is_success'	=> false,
				'error'			=> 'Unrecognized Peer'
			);
		}

		# first response is server version

		$cmd = array(
			'action'	=> 'check',
			'object'	=> 'version',
			'attributes'	=> array(
				'sender'	=> 'OpenSRS CLIENT',
				'version'	=> $this->VERSION,
				'state'		=> 'ready'
			)
		);
		$this->send_data( $cmd );

		$cmd = array(
			'action'		=> 'authenticate',
			'object'		=> 'user',
			'attributes'	=> array(
				'crypt_type'	=> strtolower($this->crypt_type),
				'username'		=> $username,
				'password'		=> $username
			)
		);
		$this->send_data( $cmd );

		$challenge = $this->read_data( array('no_xml'=>true,'binary'=>true) );

		# Respond to the challenge with the MD5 checksum of the challenge.
		# note the use of the no_xml => 1 set, because challenges are
		# are sent without XML

		# ... and PHP's md5() doesn't return binary data, so
		# we need to pack that too


		$this->_CBC = new Crypt_CBC(pack('H*', $private_key), $this->crypt_type );

		$response = pack('H*',md5($challenge));

		$this->send_data( $response, array('no_xml'=>true,'binary'=>true) );

		# Read the server's response to our login attempt (XML)

		$answer = $this->read_data();


		if (isset($answer['response_code']) && substr($answer['response_code'],0,1)== '2') {
			$this->_authenticated = true;
			return array('is_success' => true);
		} else {
	                ob_start();
			echo "<pre>";
	                print_r($answer);
			echo "</pre>";
	                $temp .= ob_get_contents() . "\n";
			ob_end_clean();

			return array(
				'is_success'	=> false,
				'error'			=> 'Authentication failed',
				'answer'	=> $temp
			);
		}
	}


#
#	lookup_domain()
#		Special case for domain lookups
#
#
#	NOTE: I have changed the error codes returned by this function.
#	Instead of having all syntax errors return a 400 code, they return
#	codes in the range 490 to 499 range, depending on the type of error.
#	This makes it much easier to determine what *kind* of lookup error
#	happened, by having *us* parse through response_text for various strings.
#
#		490	No domain given
#		491	TLD not supported
#		492	Domain name too long
#		493	Invalid characters
#		499	Other error
#
#	Syntax errors coming back from the server will likely still have error
#	code 400.  Oh well.
#


	function lookup_domain($lookupData) {

		$domain = strtolower($lookupData['attributes']['domain']);
		$affiliate_id = $lookupData['attributes']['affiliate_id'];

		if ($domain=='') {
			$data = array(
				'is_success'	=> false,
				'response_code'	=> 490,
				'response_text'	=> "Invalid syntax: no domain given."
			);
			return $data;
		}

		$syntaxError = $this->check_domain_syntax($domain);

		if ($syntaxError) {

# START of new error stuff
			$code = 499;
			if (strstr($syntaxError, 'Top level domain in')) {
				$code = 491;
			} else if (strstr($syntaxError, 'Domain name exceeds maximum length')) {
				$code = 492;
			} else if (strstr($syntaxError, 'Invalid domain format')) {
				$code = 493;
			}
# END of new error stuff

			$data = array(
				'is_success'	=> false,
				'response_code'	=> $code,
				'response_text'	=> "Invalid domain syntax for $domain: $syntaxError."
			);
			return $data;
		}

		# attempt to find other available matches if requested in conf file

		$domains = array();

		preg_match('/(.+)'.$this->OPENSRS_TLDS_REGEX.'$/', $domain, $temp);
		$base = $temp[1];
		$tld = $temp[2];

		$relatedTLDs = $this->getRelatedTLDs( $tld );
		if ($this->lookup_all_tlds && is_array($relatedTLDs)) {
			$domains = array();
			foreach($relatedTLDs as $stem) {
				$domains[] = $base.$stem;
			}
		} else {
			$domains[] = $domain;
		}

		$data = array();

		foreach($domains as $local_domain) {
			$lookupData['attributes']['domain'] = $local_domain;

			# send request to server
	
			$this->send_data( $lookupData );
			$answer = $this->read_data( );

			if ( $answer['attributes']['status'] &&
			  stristr($answer['attributes']['status'],'available') &&
			  !stristr($local_domain,$domain) ) {

				$data['attributes']['matches'][] = $local_domain;
			}


			# The original domain in the lookup determines
			# the overall return values

			if ($local_domain==$domain) {
				$data['is_success']	= $answer['is_success'];
				$data['response_code']	= $answer['response_code'];
				$data['response_text']	= $answer['response_text'];
				$data['attributes']['status'] = $answer['attributes']['status'];
				if(isset($answer['attributes']['upg_to_subdomain'])){
					$data['attributes']['upg_to_subdomain'] = $answer['attributes']['upg_to_subdomain'];
				}
				if(isset($answer['attributes']['reason'])){
					$data['attributes']['reason'] = $answer['attributes']['reason'];
				}

			}
		}

		return $data;

	}


#
#	close_socket()
#		Close the socket connection
#

	function close_socket() {
		fclose($this->_socket);
		if ($this->_CBC) {
			$this->_CBC->_Crypt_CBC();			/* destructor */
		}
		$this->_CBC				= false;
		$this->_authenticated	= false;
		$this->_socket			= false;
		$this->_log('i','Socket closed');
	}


#
#	read_data()
#		Reads a response from the server
#

	function read_data($args=array()) {

		$buf = $this->_OPS->readData($this->_socket, $this->read_timeout);

		if (!$buf) {
			$data = array('error' => 'Read error');
			$this->_log('i',$data);
		} else {
			$data = $this->_CBC ? $this->_CBC->decrypt($buf) : $buf;
			if (!isset($args['no_xml']) || !$args['no_xml']) {
				$data = $this->_OPS->decode($data);
			}
			if (isset($args['binary']) && $args['binary']) {
				$temp = unpack('H*temp', $data);
				$this->_log('r', 'BINARY: ' . $temp['temp'] );
			} else {
				$this->_log('r',$data);
			}
		}

		return $data;
	}


#
#	send_data()
#		Sends request to the server
#

	function send_data($message, $args=array()) {

		if (!isset($args['no_xml']) || !$args['no_xml']) {
			$message['protocol'] = $this->protocol;
			$data_to_send = $this->_OPS->encode( $message );

			# have to lowercase the action and object keys
			# because OPS.pm uppercases them

			$message['action'] = strtolower($message['action']);
			$message['object'] = strtolower($message['object']);
		} else {
			# no XML encoding
			$data_to_send = $message;
		}

		if (isset($args['binary']) && $args['binary']) {
			$temp = unpack('H*temp', $message);
			$this->_log('s', 'BINARY: ' . $temp['temp'] );
		} else {
			$this->_log('s', $message);
		}

		if ($this->_CBC) {
			$data_to_send = $this->_CBC->encrypt($data_to_send);
		}

		return $this->_OPS->writeData( $this->_socket, $data_to_send );
	}


#
#	check_email_syntax()
#		Regex check for valid email
#

	function check_email_syntax($email) {
		if ( preg_match('/(@.*@)|(\.\.)|(@\.)|(\.@)|(^\.)/', $email) ||
			!preg_match('/^\S+\@(\[?)[a-zA-Z0-9\-\.]+\.([a-zA-Z]{2,3}|[0-9]{1,3})(\]?)$/', $email) ) {
			return false;
		} else {
			return true;
		}
	}


#
#	check_domain_syntax()
#		Regex check for valid domain
#

	function check_domain_syntax($domain) {

		global $OPENSRS_TLDS_REGEX;

		$domain = strtolower($domain);

		$MAX_UK_LENGTH = 61;
		$MAX_NSI_LENGTH = 67;

		if (substr($domain,-3)=='.uk') {
			$maxLengthForThisCase = $MAX_UK_LENGTH;
		} else {
			$maxLengthForThisCase = $MAX_NSI_LENGTH;
		}

		if (strlen($domain) > $maxLengthForThisCase) {
			return "Domain name exceeds maximum length for registry ($maxLengthForThisCase)";
		} else if (!preg_match('/'.$OPENSRS_TLDS_REGEX.'$/', $domain)) {
			return "Top level domain in \"$domain\" is unavailable";
		} else if (!preg_match('/^[a-zA-Z0-9][.a-zA-Z0-9\-]*[a-zA-Z0-9]'.$this->OPENSRS_TLDS_REGEX.'$/', $domain)) {
			return "Invalid domain format (try something similar to \"yourname.com\")";
		}
		return false;
	}


#
#	prune_private_keys()
#		Recursively remove keys that start with 'p_' from an array
#

	function prune_private_keys(&$data) {
		if (is_array($data) || is_object($data)) {
			foreach($data as $key=>$value) {
				if (substr($key,0,2)=='p_') {
					unset($data[$key]);
				} else if (is_array($value)) {
					$this->prune_private_keys($value);
				}
			}
		}
	}


#
#	getRelatedTLDs()
#

	function getRelatedTLDs($tld) {
		if (isset($this->RELATED_TLDS) && is_array($this->RELATED_TLDS)) {
			foreach($this->RELATED_TLDS as $relatedTLDs) {
				foreach ($relatedTLDs as $TLDstring) {
					if ($TLDstring==$tld) {
						return $relatedTLDs;
					}
				}
			}
		}
		return array();
	}



#
#	_log()
#		Internal logging method
#

	function _log($type,$data) {

		$types = array(
			'i'	=> 'Info',
			'r'	=> 'Read',
			's'	=> 'Sent'
		);

		$temp = sprintf("[ %s%s ]\n",
			strtoupper($types[$type]),
			(($type!='i' && $this->_CBC) ? ' - '.$this->crypt_type.' ENCRYPTED' : '')
		);

		ob_start();
		print_r($data);
		$temp .= ob_get_contents() . "\n";
		ob_end_clean();

		$this->log[] = $temp;

	}

#
#	showlog()
#		output the debugging log
#

	function showlog() {
		echo '<PRE>';
		foreach ($this->log as $line) {
			echo htmlEntities($line) . "\n";
		}
		echo '</PRE>';
	}



}
