原文: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:
- EPP Testbed password
- Confirm EPP Testbed password
- 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.
pipesnamed 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.
- Service starts and maintains a connection with the EPP server. If connection fails report an error.
- Service listens on socket for connections.
- Client opens socket and sends XML through socket.
- Service creates clTRID and associates it with socket/client connection.
- Service sends XML to server.
- Service parses XML responses looking for trID.
- Service looks up trID and sends XML to correct socket/client connection.
- 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.
- 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:
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 */
/* Socket Libraries */
/* GnuTLS */
/* 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 EPP_TLS_CAFILE "/etc/ssl/certs/Verisign_Class_3_Public_Primary_Certification_Authority.pem"
//#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);
printf("%s resolved to %s\n", hostname, ip);
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);
}
printf("∨∨∨---From Client---∨∨∨\n");
printf("%s\n",MSG);
gnutls_record_send(session, MSG, strlen(MSG));
printf("∧∧∧---From Client---∧∧∧\n");
printf("∨∨∨---From Server---∨∨∨\n");
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));
}
printf("∧∧∧---From Server---∧∧∧\n");
gnutls_bye(session, GNUTLS_SHUT_RDWR);
gnutls_deinit(session);
close(connfd);
free(connfdPtr);
gnutls_certificate_free_credentials(x509_cred);
gnutls_global_deinit();
printf("All done!\n");
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)
{
printf("Connecting to %s\n",ip);
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;
printf("hostname: %s\n",hostname);
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;
}
printf("%s\n", out.data);
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.