怎样用C进行EPP连接

2024年2月3日 | 分类: 【技术】

原文:https://web.johncook.uk/articles/computing/nominet-epp-client

Creating a Nominet-Compatible EPP Client

With Nominet shutting down the Automaton, and the Web client no good for anything other than manual changes by the registrar, I have decided to have a play around with EPP.

Nominet EPP Testbed Registration

In order to use the Nominet EPP Testbed, you need to register to use it. Sign in to Nominet Online Services with your Registrar Account, and in Tag Settings go to EPP Testbed Settings.

There are 3 fields that need entering here:

  1. EPP Testbed password
  2. Confirm EPP Testbed password
  3. IP Address(es) for EPP Testbed

Password

The EPP Testbed password must (at the time of writing) be a maximum of 16 alpha-numeric characters—I tested various secure passwords starting with 63 printable characters until Nominet stopped giving informative error messages and the password entered was valid.

To get a 16 character alpha-numeric password that is pseudo-random in Linux, use the following command:

< /dev/urandom tr -dc 'A-Za-z0-9' | head -c16; echo;
lh9lHlyDEI5bKmcj

While you can change your EPP (Testbed/Live) password using EPP, in the case of Nominet’s EPP Testbed a password change will not persist past the next reset cycle.

IP Address(es)

Nominet restrict usage by IP address, rather than SSL client certificates, for the .uk TLD.

A maximum of 10 IP addresses can be input in the EPP Testbed Settings page.

It takes an hour for changes to be made to EPP Testbed settings (although elsewhere it says until the next reset cycle, so it might just be an hour when setting up the first time and a day for modifications), and up to a day for changes to be made to the EPP Live settings.

Since I am currently testing, I have decided to use my semi-static home IP address and my static VPS IP address. Both IP addresses I’m referring to are IPv4, although Nominet have AAAA records (via CNAME records) for both testbed-epp.nominet.org.uk and epp.nominet.org.uk.

login, hello, logout

xmllint can be used to verify your XML is valid.

For EPP, Nominet’s Schemas and Namespaces page includes various bundles including the Standard EPP with Nominet optional extensions bundle.

Included in that bundle (version 1.0.9) is nom-root-std-1.0.9.xsd which can be used to validate EPP XML before sending to Nominet’s EPP Testbed.

So, with an XML file called login.xml, and the bundle extracted to ./nom-std-1.0.9-schemas/, I can validate login.xml with the following command:

xmllint --noout --schema ./nom-std-1.0.9-schemas/nom-root-std-1.0.9.xsd login.xml
login.xml validates

login.xml

The login command authenticates you and sets up a session.

<?xml version="1.0" encoding="UTF-8"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="urn:ietf:params:xml:ns:epp-1.0 epp-1.0.xsd">
	<command>
		<login>
			<clID>__JOHNCOOK__</clID>
			<pw>__SuperSecretPasswordIsInvalid__</pw>
			<options>
				<version>1.0</version>
				<lang>en</lang>
			</options>
			<svcs>
				<objURI>urn:ietf:params:xml:ns:domain-1.0</objURI>
				<objURI>urn:ietf:params:xml:ns:contact-1.0</objURI>
				<objURI>urn:ietf:params:xml:ns:host-1.0</objURI>
			</svcs>
		</login>
	</command>
</epp>

hello.xml

The hello command acts as a keep-alive, with Nominet suggesting sending hello after 59 minutes of idle time.

<?xml version="1.0" encoding="UTF-8"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="urn:ietf:params:xml:ns:epp-1.0
	epp-1.0.xsd">
	<hello/>
</epp>

logout.xml

The logout command closes a session cleanly.

<?xml version="1.0" encoding="UTF-8"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="urn:ietf:params:xml:ns:epp-1.0 epp-1.0.xsd">
	<command>
		<logout/>
	</command>
</epp>

Connecting to the Testbed

There are two testbed servers made available to Nominet registrars: one that uses SSLv3 and another that uses plain text. The SSLv3 server certificate is issued by the Verisign Class 3 Public Primary Certification Authority root certificate.

The testbed server’s hostname is testbed-epp.nominet.org.uk, and it listens on port 700 for SSLv3 connections (upon testing TLS 1.2 is supported) and port 8700 for plain text connections.

With login.xml, hello.xml, and logout.xml to hand (for copying and pasting) it is now possible to test connecting to the EPP Testbed.

openssl s_client -CAfile /etc/ssl/certs/Verisign_Class_3_Public_Primary_Certification_Authority.pem -connect testbed-epp.nominet.org.uk:700

Upon connection you receive a greeting:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://www.nominet.org.uk/epp/xml/epp-1.0 epp-1.0.xsd">
  <greeting>
    <svID>Nominet EPP server testbed-epp.nominet.org.uk</svID>
    <svDate>2015-11-02T16:05:16Z</svDate>
    <svcMenu>
      <version>1.0</version>
      <lang>en</lang>
      <objURI>http://www.nominet.org.uk/epp/xml/nom-abuse-feed-1.0</objURI>
      <objURI>http://www.nominet.org.uk/epp/xml/nom-dss-1.0</objURI>
      <objURI>http://www.nominet.org.uk/epp/xml/nom-reseller-1.0</objURI>
      <objURI>http://www.nominet.org.uk/epp/xml/nom-tag-1.0</objURI>
      <objURI>urn:ietf:params:xml:ns:contact-1.0</objURI>
      <objURI>urn:ietf:params:xml:ns:domain-1.0</objURI>
      <objURI>urn:ietf:params:xml:ns:host-1.0</objURI>
      <svcExtension>
        <extURI>http://www.nominet.org.uk/epp/xml/contact-nom-ext-1.0</extURI>
        <extURI>http://www.nominet.org.uk/epp/xml/domain-nom-ext-1.0</extURI>
        <extURI>http://www.nominet.org.uk/epp/xml/domain-nom-ext-1.1</extURI>
        <extURI>http://www.nominet.org.uk/epp/xml/domain-nom-ext-1.2</extURI>
        <extURI>http://www.nominet.org.uk/epp/xml/nom-data-quality-1.0</extURI>
        <extURI>http://www.nominet.org.uk/epp/xml/nom-data-quality-1.1</extURI>
        <extURI>http://www.nominet.org.uk/epp/xml/nom-direct-rights-1.0</extURI>
        <extURI>http://www.nominet.org.uk/epp/xml/std-contact-id-1.0</extURI>
        <extURI>http://www.nominet.org.uk/epp/xml/std-fork-1.0</extURI>
        <extURI>http://www.nominet.org.uk/epp/xml/std-handshake-1.0</extURI>
        <extURI>http://www.nominet.org.uk/epp/xml/std-list-1.0</extURI>
        <extURI>http://www.nominet.org.uk/epp/xml/std-locks-1.0</extURI>
        <extURI>http://www.nominet.org.uk/epp/xml/std-notifications-1.0</extURI>
        <extURI>http://www.nominet.org.uk/epp/xml/std-notifications-1.1</extURI>
        <extURI>http://www.nominet.org.uk/epp/xml/std-notifications-1.2</extURI>
        <extURI>http://www.nominet.org.uk/epp/xml/std-release-1.0</extURI>
        <extURI>http://www.nominet.org.uk/epp/xml/std-unrenew-1.0</extURI>
        <extURI>http://www.nominet.org.uk/epp/xml/std-warning-1.1</extURI>
        <extURI>urn:ietf:params:xml:ns:secDNS-1.1</extURI>
      </svcExtension>
    </svcMenu>
    <dcp>
      <access><all/></access>
      <statement>
        <purpose><admin/><prov/></purpose>
        <recipient><ours/></recipient>
        <retention><indefinite/></retention>
      </statement>
    </dcp>
  </greeting>
</epp>

Upon pasting the contents of login.xml, the response XML should contain a result code of 1000 if everything is correct:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.nominet.org.uk/epp/xml/epp-1.0 epp-1.0.xsd">
  <response>
    <result code="1000">
      <msg>
        Command completed successfully
      </msg>
    </result>
    <trID>
      <svTRID>__RANDOMNUM__</svTRID>
    </trID>
  </response>
</epp>

Pasting the hello command (contents of hello.xml) will result in a greeting response. Unlike the greeting response receiving when first connecting, this response will be shorter because we said we only wanted to use some <svcs> and no <svcExtension>s:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.nominet.org.uk/epp/xml/epp-1.0 epp-1.0.xsd">
    <greeting>
    <svID>Nominet EPP server testbed-epp.nominet.org.uk</svID>
    <svDate>2015-11-02T16:13:08Z</svDate>
    <svcMenu>
      <version>1.0</version>
      <lang>en</lang>
      <objURI>urn:ietf:params:xml:ns:contact-1.0</objURI>
      <objURI>urn:ietf:params:xml:ns:domain-1.0</objURI>
      <objURI>urn:ietf:params:xml:ns:host-1.0</objURI>
      <svcExtension>
        <extURI>http://www.nominet.org.uk/epp/xml/std-warning-1.1</extURI>
      </svcExtension>
    </svcMenu>
    <dcp>
      <access><all/></access>
      <statement>
        <purpose><admin/><prov/></purpose>
        <recipient><ours/></recipient>
        <retention><indefinite/></retention>
      </statement>
    </dcp>
  </greeting>
</epp>

Finally, pasting the contents of logout.xml (the logout command) will gracefully close the session. EPP is designed to be always connected, so the logout command should rarely need to be used (e.g. before rebooting the machine), but this was just a connection test.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.nominet.org.uk/epp/xml/epp-1.0 epp-1.0.xsd">
  <response>
    <result code="1500">
      <msg>
        Command completed successfully; ending session
      </msg>
    </result>
    <trID>
      <svTRID>__RANDOMNUM__</svTRID>
    </trID>
  </response>
</epp>read:errno=0

Transaction IDs

EPP provides optional transaction tracking through the use of the <clTRID> (client transaction identifier) element.

  -  An OPTIONAL <clTRID> (client transaction identifier) element that
      MAY be used to uniquely identify the command to the client.
      Clients are responsible for maintaining their own transaction
      identifier space to ensure uniqueness.

IETF. Command Format. In Extensible Provisioning Protocol (EPP), RFC 5730 (§2.5)

In a repsopnse from the server, there will be a <trID> (transaction identifier) element containing a <clTRID> element (if supplied by the client) and an <svTRID> element.

The <clTRID> element will have identical content to that sent by the client for that transaction, if one was sent. If no <clTRID> was sent by the client the element will be missing from the server’s response.

The <svTRID> (server transaction identifier) element is the server’s equivalent of <clTRID> in that it should be unique and the server is responsible for generating it and ensuring it is unique.

-  A <trID> (transaction identifier) element containing the
      transaction identifier assigned by the server to the command for
      which the response is being returned.  The transaction identifier
      is formed using the <clTRID> associated with the command if
      supplied by the client and a <svTRID> (server transaction
      identifier) that is assigned by and unique to the server.

IETF. Response Format. In Extensible Provisioning Protocol (EPP), RFC 5730 (§2.6)

Connection Works and Commands Successful

This isn’t yet an EPP client, it is just a successful connection test.

The next step is to create a daemon that can maintain a connection, and relay commands from a client to the server and responses from the server to a client.

This is going to be a challenge, as I have never coded a daemon before.


What On Earth Do I Want To Do?

Pipes, sockets, and redirects are things I know a bit about and could somewhat be used for what I want to do, but I haven’t a clue what I’m supposed to be Googling for.

Do I want to program something in C? Not if I can avoid it.

This is the point where rather than Googling something I need to Youtube something.

  • pipes
  • named pipes
  • POSIX Message Queues (MQ)? POSIX = C Programming?
  • TCP or Unix Domain Sockets?

My rudimentary thoughts on how the service could work is listed below. I need it to maintain a connection to the EPP server, for which I am thinking the GnuTLS C API might suffice.

I also need to make sure that clients to the service do not interfere with each other—a worst case scenario would be simultaneous connections causing two XML inputs to become interleaved during sending to EPP server.

  1. Service starts and maintains a connection with the EPP server. If connection fails report an error.
  2. Service listens on socket for connections.
  3. Client opens socket and sends XML through socket.
  4. Service creates clTRID and associates it with socket/client connection.
  5. Service sends XML to server.
  6. Service parses XML responses looking for trID.
  7. Service looks up trID and sends XML to correct socket/client connection.
  8. Service closes socket connection to client.

I am only just learning how to program in C, and about POSIX, so I don’t yet know how I’d achieve the above.

I’d also need a way for the service to close the socket while the EPP server is unavailable during Nominet maintenance (or Internet connection outages).

If the service creates the clTRID, that will mean modifying the XML—clients might be relying on that. Maybe it should just parse the XML?

Loops and Forking

So far it looks like using a read loop for connections, and forking on an incoming connection (that blocks until the forked process closes) looks like it might suffice.

While the forked process is waiting for the EPP server to respond, no new commands will be sent to the EPP server. That means that the service–server connection will be serialised (waiting for a response before sending another command).

A potential problem is if the server sends a response that is not intended for that client, so checking the <clTRID> matches will be needed (although this would just be sanity checking because the EPP server shouldn’t send anything, except on first connection, without it being in response to an EPP client request).

   With the exception of the EPP server greeting, EPP messages are
   initiated by the EPP client in the form of EPP commands.
[...]
   EPP describes client-server interaction as a command-response
   exchange where the client sends one command to the server and the
   server returns one response to the client.  A client might be able to
   realize a slight performance gain by pipelining (sending more than
   one command before a response for the first command is received)
   commands with TCP transport, but this feature does not change the
   basic single command, single response operating mode of the core
   protocol.

   Each EPP data unit MUST contain a single EPP message.  Commands MUST
   be processed independently and in the same order as sent from the
   client.

IETF. Message Exchange. In Extensible Provisioning Protocol (EPP) Transport over TCP, RFC 5734 (§3)

In other words, forking and blocking while waiting for a response would be completely compliant with the RFC, even if it doesn’t “realize a slight performance gain” that could be possible with pipelining.

Given each EPP command MUST be processed (and responded to) in the order they are received, and pipelining isn’t something I plan on doing (for now, anyway), forking looks like it is the way I will go.

Parsing EPP Responses

As well as checking the XML is valid (and contains a matching <clTRID>) the response is not pure XML.

What I mean by this is the server doesn’t just send XML, it also sends something else: the total length of the response.

  1. Data Unit Format
   The EPP data unit contains two fields: a 32-bit header that describes
   the total length of the data unit, and the EPP XML instance.  The
   length of the EPP XML instance is determined by subtracting four
   octets from the total length of the data unit.  A receiver must
   successfully read that many octets to retrieve the complete EPP XML
   instance before processing the EPP message.

   EPP Data Unit Format (one tick mark represents one bit position):

       0                   1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                           Total Length                        |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                         EPP XML Instance                      |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   Total Length (32 bits): The total length of the EPP data unit
   measured in octets in network (big endian) byte order.  The octets
   contained in this field MUST be included in the total length
   calculation.

   EPP XML Instance (variable length): The EPP XML instance carried in
   the data unit.

IETF. Data Unit Format. In Extensible Provisioning Protocol (EPP) Transport over TCP, RFC 5734 (§4)

I have trimmed it from the EPP responses above as the 32-bit header isn’t text (and not technically part of the XML response), but what this can allow you to do is wait until you can read 4 bytes from the stream/connection, subtract 4 from the decimal value of those 4 bytes, and then read the resultant number of bytes from the stream/connection.

For example, if the XML response is 48 bytes, it will be prefaced with 608 or 4810 or 3016. Hex 30 (\x30) in UTF-8 is the character 0 (zero). If the length were 4000 bytes, then 76408 (fa016) would be the header (with zero padding to make it 32 bits long).

For my login to EPP, for example, the length of response (including spaces) was 431 characters. Add 4 (for the 32 bit header) and that is 435 bytes. 43510 is 1b316.

To see what the start of that response would look like in your terminal, the following command will show you:

echo -e '\x1\xb3<?xml version="1.0" encoding="UTF-8" standalone="no"?>'

Using the GnuTLS C API

First, install the needed development packages:

sudo apt-get install build-essential libgnutls28-dev gdb manpages-dev manpages-posix-dev

Then create a very simple C file, which I’ll call connection.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>

int main(void)
{
        return 0;
}

Then compile it and make sure it doesn’t return any errors (it shouldn’t unless the compiler or the GnuTLS development libraries aren’t installed):

gcc -o connection connection.c -lgnutls

Not exactly the most useful code for my first ever C program… it doesn’t even say hello world.

Rather than testing with the EPP server, I am going to be testing on a local TLS-enabled server. In this case webmail.thejc.me.uk which is secured with a certificate issued by StartCom.

Since HTTP/1.1 (unlike HTTP/1.0) doesn’t close a connection after a response, this makes it suitable as a first test of opening and maintaining a connection to a TLS-encrypted server.

A Working TLS Client

After a lot of Googling, and trial and error, I reached the following working code that will likely be a base for my EPP client (permanent filename: connection-2015-11-04R004.c):

/* Standard Libraries */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
/* Socket Libraries */
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
/* GnuTLS */
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>

/* Connection Variables:
	BIND_ADDR: Local IPv4 IP address formatted as IPv4-mapped-IPv6 (e.g. ::ffff:127.0.0.1).
	BIND_ADDR6: Local IPv6 IP address.
	EPP_HOSTNAME: string for EPP Server's IP address or hostname.
	EPP_TLS_PORT: string for EPP Server's TLS port.
	EPP_TLS_CAFILE: string for full path to EPP Server's Root CA file.
	EPP_TLS_CIPHERS: string for cipher list - enabled and disabled ciphers (GnuTLS format).
	MSG: string sent to server on successful connection.
	LOG_LEVEL: integer between 0 and 9 for setting GnuTLS log level.
	COMMENTS: uncomment to enable more verbose commenting for debugging purposes.
*/
#define BIND_ADDR "::ffff:82.26.77.204"
#define BIND_ADDR6 "2001:470:1f09:1aab::80:d"
#define EPP_HOSTNAME "webmail.thejc.me.uk"
#define EPP_TLS_PORT "443"
#define EPP_TLS_CAFILE "/etc/ssl/certs/StartCom_Certification_Authority.pem"
//#define EPP_TLS_CAFILE "/etc/ssl/certs/Verisign_Class_3_Public_Primary_Certification_Authority.pem"
#define EPP_TLS_CIPHERS "PFS"
#define MSG "GET / HTTP/1.0\r\nhost: webmail.thejc.me.uk\r\nUser-agent: EPP Client\r\n\r\n"
#define LOG_LEVEL 0
//#define COMMENTS

void error_exit(const char *msg);
ssize_t data_push(gnutls_transport_ptr_t, const void*, size_t);
ssize_t data_pull(gnutls_transport_ptr_t, void*, size_t);
void print_logs(int, const char*);
void print_audit_logs(gnutls_session_t, const char*);
int make_one_connection(const char *address, int port);
int hostname_to_ip(char *, char *);
int verify_cert(struct gnutls_session_int *);

int main(int argc, char **argv)
{
	int res;
	gnutls_certificate_credentials_t x509_cred;

	gnutls_global_init();

	gnutls_global_set_log_level(LOG_LEVEL);
	gnutls_global_set_log_function(print_logs);

	gnutls_session_t session;

	gnutls_certificate_allocate_credentials(&x509_cred);
	gnutls_certificate_set_x509_trust_file(x509_cred, EPP_TLS_CAFILE, GNUTLS_X509_FMT_PEM);

	res = gnutls_init(&session, GNUTLS_CLIENT);
	if (res != GNUTLS_E_SUCCESS) {
		printf("Error code %d in gnutls_init(): %s\n",res,gnutls_strerror(res));
		exit(1);
	}

	gnutls_session_set_ptr(session, (void *) EPP_HOSTNAME);
	gnutls_server_name_set(session, GNUTLS_NAME_DNS, EPP_HOSTNAME, strlen(EPP_HOSTNAME));

	const char *error = NULL;
	res = gnutls_priority_set_direct(session, EPP_TLS_CIPHERS, &error);
	if (res != GNUTLS_E_SUCCESS) {
		printf("Invalid Cipher: %s\n",error);
		printf("Error code %d in gnutls_priority_set_direct(): %s\n",res,gnutls_strerror(res));
		exit(1);
	}

	gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred);


	char *hostname = EPP_HOSTNAME;
	char ip[INET6_ADDRSTRLEN];
	hostname_to_ip(hostname, ip);
#ifdef COMMENTS
	printf("%s resolved to %s\n", hostname, ip);
#endif

	int connfd = make_one_connection(ip, atoi(EPP_TLS_PORT));

	int *connfdPtr = malloc(sizeof(int));
	*connfdPtr = connfd;
	gnutls_transport_set_ptr(session, connfdPtr);
	gnutls_transport_set_push_function(session, data_push);
	gnutls_transport_set_pull_function(session, data_pull);

	gnutls_certificate_set_verify_function(x509_cred, verify_cert);

	do {
		res = gnutls_handshake(session);
	} while (res != 0 && !gnutls_error_is_fatal(res));

	if (gnutls_error_is_fatal(res)) {
		printf("Error code %d in gnutls_handshake(): %s\n",res,gnutls_strerror(res));
		exit(1);
	}


#ifdef COMMENTS
	printf("∨∨∨---From Client---∨∨∨\n");
#endif
	printf("%s\n",MSG);
	gnutls_record_send(session, MSG, strlen(MSG));
#ifdef COMMENTS
	printf("∧∧∧---From Client---∧∧∧\n");

	printf("∨∨∨---From Server---∨∨∨\n");
#endif
	char buf[256];
	res = gnutls_record_recv(session, buf, sizeof(buf));
	while (res != 0) {
		if (res == GNUTLS_E_REHANDSHAKE) {
			printf("Error code %d: %s\n",res,gnutls_strerror(res));
			error_exit("Peer wants to re-handshake but we don't support that.\n");
		} else if (gnutls_error_is_fatal(res)) {
			printf("Error code %d: %s\n",res,gnutls_strerror(res));
			error_exit("Fatal error during read.\n");
		} else if (res > 0) {
			fwrite(buf, 1, res, stdout);
			fflush(stdout);
		}
		res = gnutls_record_recv(session, buf, sizeof(buf));
	}
#ifdef COMMENTS
	printf("∧∧∧---From Server---∧∧∧\n");
#endif


	gnutls_bye(session, GNUTLS_SHUT_RDWR);
	gnutls_deinit(session);
	close(connfd);
	free(connfdPtr);
	gnutls_certificate_free_credentials(x509_cred);
	gnutls_global_deinit();
#ifdef COMMENTS
	printf("All done!\n");
#endif

	return 0;
}

/* function hostname_to_ip is a modified version of:
	https://gist.github.com/twslankard/1001201
	IPv6 support added, but no preference given.
	First A/AAAA IP address listed by DNS resolver will be used.
	Variables:
	ipv4off: set to 1 to disable the return of an IPv4 address.
	ipv6off: set to 1 to disable the return of an IPv6 address.
*/
int hostname_to_ip(char *hostname, char *ip)
{
	int ipv4off = 0;
	int ipv6off = 1;
	struct addrinfo * _addrinfo;
	struct addrinfo * _res;
	int errorcode = 0;

	if (ipv4off != 0 && ipv6off != 0) {
		printf("hostname_to_ip error: both IPv4 and IPv6 is disabled.\n");
		exit(1);
	}

	errorcode = getaddrinfo(hostname, EPP_TLS_PORT, NULL, &_addrinfo);
	if (errorcode != 0) {
		printf("getaddrinfo: %s\n", gai_strerror(errorcode));
		exit(1);
	}

	for (_res = _addrinfo; _res != NULL; _res = _res->ai_next) {
		if (_res->ai_family == AF_INET && ipv4off == 0) {
			if (NULL == inet_ntop(AF_INET, &((struct sockaddr_in *)_res->ai_addr)->sin_addr, ip, INET6_ADDRSTRLEN)) {
				perror("inet_ntop");
				exit(1);
			} else {
				char * ipv4_mapping;
				ipv4_mapping = "::ffff:";
				char * ipv4_mapped = (char *) malloc(1 + strlen(ipv4_mapping) + strlen(ip));
				strcpy(ipv4_mapped, ipv4_mapping);
				strcat(ipv4_mapped, ip);
				strcpy(ip, ipv4_mapped);
				free(ipv4_mapped);
				return 0;
			}
		}
		if (_res->ai_family == AF_INET6 && ipv6off == 0) {
			if (NULL == inet_ntop(AF_INET6, &((struct sockaddr_in6 *)_res->ai_addr)->sin6_addr, ip, INET6_ADDRSTRLEN)) {
				perror("inet_ntop");
				exit(1);
			} else {
				return 0;
			}
		}
	}

	return 0;
}

void print_logs(int level, const char* msg)
{
	printf("GnuTLS [%d]: %s", level, msg);
}

void print_audit_logs(gnutls_session_t session, const char* message)
{
	printf("GnuTLS Audit: %s", message);
}

void error_exit(const char *msg)
{
	printf("ERROR: %s", msg);
	exit(1);
}


ssize_t data_push(gnutls_transport_ptr_t ptr, const void* data, size_t len)
{
	int sockfd = *(int*)(ptr);
	return send(sockfd, data, len, 0);
}

ssize_t data_pull(gnutls_transport_ptr_t ptr, void* data, size_t maxlen)
{
	int sockfd = *(int*)(ptr);
	return recv(sockfd, data, maxlen, 0);
}



int make_one_connection(const char *ip, int port)
{
#ifdef COMMENTS
	printf("Connecting to %s\n",ip);
#endif
	int res;
	int connfd = socket(AF_INET6, SOCK_STREAM, 0);

	char *local_bind_address = BIND_ADDR6;
	size_t len = strlen(ip);
	size_t spn = strcspn(ip, ".");
	if (spn != len) { local_bind_address = BIND_ADDR; }
	struct sockaddr_in6 local_addr;
	if (connfd < 0) {
		error_exit("socket() failed.\n");
	}
	local_addr.sin6_family = AF_INET6;
	res = inet_pton(AF_INET6, local_bind_address, &local_addr.sin6_addr);
	local_addr.sin6_port = 0;
	res = bind(connfd, (struct sockaddr *)&local_addr, sizeof(local_addr));
	if (res < 0) {
		error_exit("bind() failed. Ensure BIND_ADDR and/or BIND_ADDR6 IP addresses exist on this system.\n");
	}

	struct sockaddr_in6 serv_addr;
	if (connfd < 0) {
		error_exit("socket() failed.\n");
	}
	serv_addr.sin6_family = AF_INET6;
	res = inet_pton(AF_INET6, ip, &serv_addr.sin6_addr);
	serv_addr.sin6_port = htons(port);
	res = connect(connfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
	if (res < 0) {
		error_exit("connect() failed.\n");
	}
	return connfd;
}

int verify_cert(gnutls_session_t session) {
	unsigned int status;
	int ret, type;
	const char *hostname;
	gnutls_datum_t out;

	hostname = gnutls_session_get_ptr(session);

	gnutls_typed_vdata_st data[2];

	memset(data, 0, sizeof(data));

	data[0].type = GNUTLS_DT_DNS_HOSTNAME;
	data[0].data = (void*)hostname;
#ifdef COMMENTS
	printf("hostname: %s\n",hostname);
#endif
	data[1].type = GNUTLS_DT_KEY_PURPOSE_OID;
	data[1].data = (void*)GNUTLS_KP_TLS_WWW_SERVER;

	ret = gnutls_certificate_verify_peers(session, data, 2, &status);

	if (ret < 0) {
		printf("Error\n");
		return GNUTLS_E_CERTIFICATE_ERROR;
	}

	type = gnutls_certificate_type_get(session);
	ret = gnutls_certificate_verification_status_print(status, type, &out, 0);

	if (ret < 0) {
		printf("Error\n");
		return GNUTLS_E_CERTIFICATE_ERROR;
	}

#ifdef COMMENTS
	printf("%s\n", out.data);
#endif
	gnutls_free(out.data);


	if (status != 0) {
		return GNUTLS_E_CERTIFICATE_ERROR;
	}
	return 0;
}

What the client does is rather simple, and is mostly copied and pasted from multiple sources.

An obvious feature that is really annoying when it is missing: a local bind address. If I have multiple IP addresses available, I want to be able to tell a program “this is the IP address you should be sending from”.

Likewise: IPv6 support. Rather than duplicating code for IPv4 and IPv6 I decided to convert IPv4 DNS responses to IPv4-mapped IPv6 addresses. There is also a toggle for ignoring A or AAAA DNS results so I can force the program to connect over either IPv4 or IPv6 only.

Rudimentary certificate validation checking is working (I don’t think things like OCSP and CRL are validated).

Making it a Server

At the moment it just acts as if it were cat as it just reads a Web page and returns it on stdout.

In order to make it into a server I am going to want to keep the connection open (i.e. use HTTP/1.1). I will do that by sending the GET / HTTP/1.1 request on starting and send a keep-alive (likely through sending a HEAD request) every 30 seconds.

To somewhat replicate EPP, I am going to use chunked encoding. That means after the first \r\n\r\n there will be a line containing some hexadecimal text (as opposed to 32-bits of binary octal). This represents the number of bytes the following chunk will be. If the size is zero, all chunks have been received.

What I’m thinking is the service will connect to the Web server, and then wait for a client to connect. The client sends a GET request to the service, the service makes the GET request through the open TLS connection, and then the service returns just the message body to the client.

Returning just the message body, and doing so in one chunk, will mean there has been some parsing of the server response.

The initial connection request when starting the service will contain a response in the same way an EPP server sends a greeting upon connection. This will need to be stored, as will the keep-alive responses and the connection close (for auditing/logging purposes).

Anyway, I’ll continue learning about programming in C and I’m sure I’ll end up with something workable, as long as I don’t give up first.