Configuration File Parsing in C

In order to program an EPP Client in C I need to be able to read settings from configuration files.

Recap

In my article Creating a Nominet-Compatible EPP Client I created a simple TLS HTTP(S) client. It doesn't have much functionality, but that is because there is no interaction.

In the follow-up article TCP Sockets in C I created a TCP server which uses a header like EPP does to determine how much it needs to read from a buffer.

I added some more functionality in XML Parsing in C.

I personally would rather not pass my EPP key to my program through the use of a command line parameter or hardcode it into the source code, so in this article I am going to take a look at a way to implement configuration files in the C programming language, including programmatically modifying the configuration file (e.g. to support changing the login key within the program).

The Plan

When I asked Google Search how to parse configuration files in C a top result was for libconfig. Reading the libconfig manual it looks like it can do what I need.

Rather than creating a configuration file format and trying to find a way to parse it (or creating my own parser) I am OK with using a library and creating configuration files compatible with that library.

In the case of libconfig, the configuration files don't need to be flat. That may make it possible to make the configuration file follow the same logic as the program.

Libconfig supports structured, hierarchical configurations. These configurations can be read from and written to files and manipulated in memory.

A configuration consists of a group of settings, which associate names with values. A value can be one of the following:

  • A scalar value: integer, 64-bit integer, floating-point number, boolean, or string
  • An array, which is a sequence of scalar values, all of which must have the same type
  • A group, which is a collection of settings
  • A list, which is a sequence of values of any type, including other lists
Libconfig. Configuration Files. In libconfig manual

While the program won't be programmed for multiple connections to EPP servers, in terms of memory footprint it would make sense for the EPP server settings to be within a EPP schema setting as that would allow the schema to be reused for validating the XML within multiple connections. Something like this:

schemas:
(
	{
		bundle_file = "/home/thejc/Scripts/epp/nom-std-1.0.9-schemas/nom-root-std-1.0.9.xsd";

		servers:
		(
			{
				enabled = false;

				hostname = "testbed-epp.nominet.org.uk";
				port = "700";
				tls = true;
				tls_ca_file = "/etc/ssl/certs/DigiCert_Global_Root_CA.pem";
				tls_ciphers = "NORMAL";
				keep_alive = 59;

				xml:
				{
					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";
				};

				logins:
				(
					{
						bind_ipv4_mapped = "::ffff:82.26.77.204";
						bind_ipv6 = "";
						ipv4_only = true;
						ipv6_only = false;

						clID = "JOHNCOOK";
						pw = "lh9lHlyDEI5bKmcj";

						options:
						{
							version = "1.0";
							lang = "en";
						};

						svcs:
						{
							objURI:
							[
								"urn:ietf:params:xml:ns:domain-1.0",
								"urn:ietf:params:xml:ns:contact-1.0",
								"urn:ietf:params:xml:ns:host-1.0"
							];
						};

					}
				);

			}
		);

	}
);

In the above preliminary configuration file, schemas is a list of schemas, servers is a list of servers, logins is a list of logins, and objURI is an array of objURIs. xml is a list of name/value pairs used within the <xml> element in requests sent to the server.

I decided to store the bind addresses inside the login setting as that would allow different logins for the same server to use a different local IP addresses if desired.

Port number is a string because itoa() is less commonly available than atoi().

Includes and Linking

In order to use libconfig, I need to include the header file in my code, and I need to link to the library when compiling.

sudo apt-get install libconfig-dev
#include <libconfig.h>
gcc -o connection connection.c -lgnutls -lconfig

Compatibility Issue

In libconfig version 1.5 config_lookup_from was renamed config_setting_lookup. As Debian Jessie still uses version 1.4.9 I have added a define so I can use the functions used in the documentation even though they aren't currently in the version on my system.

#define config_setting_lookup config_lookup_from

Functions and Variables

After some thinking I decided that (for the moment) the best way of storing all the settings from the configuration file would be in a struct:

struct login_settings
{
	int enabled;
	int tls;
	int keep_alive;
	int ipv4_only;
	int ipv6_only;
	int port;
	struct config_setting_t *pointer;
	const char *lang;
	const char *version;
	const char *clID;
	const char *pw;
	const char *hostname;
	const char *bind_ipv4_mapped;
	const char *bind_ipv6;
	const char *xmlns;
	const char *xmlns_xsi;
	const char *xsi_schemaLocation;
	const char *bundle_file;
	const char *tls_ca_file;
	const char *tls_ciphers;
	const char *objURI[];
};

Some variables are also put in the global scope:

struct config_t conf;
struct config_t *config;

char *CONFIG_FILE = "/home/thejc/Scripts/epp/epp-client/epp.config";

I also have some functions defined:

int get_root_element_count(config_t *config, char *name, config_setting_t *config_element);
int get_element_count(config_setting_t *config_setting, char *name, config_setting_t *config_element);
int get_config_int(config_setting_t *setting, char *name);
int get_config_bool(config_setting_t *setting, char *name);
const char *get_config_string(config_setting_t *setting, char *name);

Those functions appear later in the file:

/*
* Function get_element_count:
*	* Returns number (int) of elements in list 'name' in configuration 'config'.
*	* Updates pointer '*config_element' to point to element 'name'.
*
* config_t *config : pointer to parsed config
* char *name : pointer to name of element
* config_setting_t *conf_element : pointer to element
*/
int get_root_element_count(config_t *config, char *name, config_setting_t *config_element)
{
	config_setting_t *conf_element = config_lookup(config, name);
	*config_element = *conf_element;
	if (config_element == NULL)
	{
		fprintf(stderr, "No %s found in configuration file.\n", name);
		exit(1);
	}
	return config_setting_length(config_element);
}

/*
* Function get_element_count:
*	* Returns number (int) of elements in list 'name' in configuration setting 'config_setting'.
*	* Updates pointer '*config_element' to point to element 'name'.
*
* config_setting_t *config : pointer to parsed config setting
* char *name : pointer to name of element
* config_setting_t *conf_element : pointer to element
*/
int get_element_count(config_setting_t *config_setting, char *name, config_setting_t *config_element)
{
	config_setting_t *conf_element = config_setting_lookup(config_setting, name);
	*config_element = *conf_element;
	if (config_element == NULL)
	{
		fprintf(stderr, "No %s found in configuration file.\n", name);
	}
	return config_setting_length(config_element);
}

/*
* Function get_config_int looks up the integer value of 'name'
*  in the configuration setting 'setting' and returns the integer.
* -1 is returned if 'name' does not exist.
*/
int get_config_int(config_setting_t *setting, char *name)
{
	config_setting_t *setting_pointer = NULL;
	setting_pointer = config_setting_lookup(setting, name);
	if (setting_pointer != NULL)
	{
		return config_setting_get_int(setting_pointer);
	}
	else
	{
		return -1;
	}
}

/*
* Function get_config_bool looks up the boolean value of 'name'
*  in the configuration setting 'setting' and returns it as an integer.
* -1 is returned if 'name' does not exist.
*/
int get_config_bool(config_setting_t *setting, char *name)
{
	config_setting_t *setting_pointer = NULL;
	setting_pointer = config_setting_lookup(setting, name);
	if (setting_pointer != NULL)
	{
		return config_setting_get_bool(setting_pointer);
	}
	else
	{
		return -1;
	}
}

/*
* Function get_config_string looks up the string value of 'name'
*  in the configuration setting 'setting' and returns the string.
* NULL is returned if 'name' does not exist.
*/
const char *get_config_string(config_setting_t *setting, char *name)
{
	config_setting_t *setting_pointer = NULL;
	setting_pointer = config_setting_lookup(setting, name);
	if (setting_pointer != NULL)
	{
		return config_setting_get_string(setting_pointer);
	}
	else
	{
		return NULL;
	}
}

And then I have the new main function (the old one has been renamed main2 and is not currently used).

int main(int argc, char **argv)
{
	config = &conf;
	config_init(config);
	int loaded_config = config_read_file(config, CONFIG_FILE);
	if (loaded_config != 1)
	{
		fprintf(stderr, "Error reading config file %s. Error on line %d: %s\n", config_error_file(config), config_error_line(config), config_error_text(config));
		config_destroy(config);
		error("config_read_file");
	}

	/*
	* Loop through schemas.
	*/
	struct config_setting_t conf_schemas;
	struct config_setting_t *config_schemas = &conf_schemas;
	int schema_count = get_root_element_count(config, "schemas", config_schemas);
#ifdef COMMENTS
	printf("Number of schemas: %d\n", schema_count);
#endif

	int i;
	for(i = 0; i < schema_count; i++)
	{
		struct config_setting_t *schema_element = config_setting_get_elem(config_schemas, i);
		if (schema_element== NULL)
		{
			continue;
		}
		struct login_settings schema_login;
		schema_login.bundle_file = get_config_string(schema_element, "bundle_file");
		schema_login.pointer = schema_element;
#ifdef COMMENTS
		printf("schemas[%d].bundle_file = %s\n", i, schema_login.bundle_file);
#endif

		/*
		* Loop through servers.
		*/
		struct config_setting_t conf_servers;
		struct config_setting_t *config_servers = &conf_servers;
		int server_count = get_element_count(schema_element, "servers", config_servers);
		if (config_servers == NULL)
		{
			fprintf(stdout, "No servers defined for schema %d.\n", i);
			continue;
		}
#ifdef COMMENTS
		printf("Number of servers using schema %d: %d\n", i, server_count);
#endif

		int j;
		for(j = 0; j < server_count; j++)
		{
			struct config_setting_t *server_element = config_setting_get_elem(config_servers, j);
			if (server_element == NULL)
			{
				continue;
			}

			struct login_settings server_login;
			memcpy(&server_login, &schema_login, sizeof(server_login));
			server_login.pointer = server_element;

			struct config_setting_t *server_setting = NULL;

			int server_setting_int = get_config_bool(server_element, "enabled");
			if (server_setting_int == 0)
			{
				fprintf(stdout, "Server %d is not enabled.\n", j);
				continue;
			}
			else if (server_setting_int > 0)
			{
				server_login.enabled = server_setting_int;
				server_login.hostname = get_config_string(server_element, "hostname");
				server_login.port = get_config_int(server_element, "port");
#ifdef COMMENTS
				printf("schemas[%d].servers[%d].enabled = %d\n", i, j, server_login.enabled);
				printf("schemas[%d].servers[%d].hostname = %s\n", i, j, server_login.hostname);
				printf("schemas[%d].servers[%d].port = %d\n", i, j, server_login.port);
#endif

				server_setting_int = get_config_bool(server_element, "tls");
				if (server_setting_int == 0)
				{
					fprintf(stdout, "Server %d is not configured for TLS. This program only supports TLS servers.\n", j);
					continue;
				}
				else if (server_setting_int > 0)
				{
					server_login.tls = server_setting_int;
					server_login.tls_ca_file = get_config_string(server_element, "tls_ca_file");
					server_login.tls_ciphers = get_config_string(server_element, "tls_ciphers");
#ifdef COMMENTS
					printf("schemas[%d].servers[%d].tls = %d\n", i, j, server_login.tls);
					printf("schemas[%d].servers[%d].tls_ca_file = %s\n", i, j, server_login.tls_ca_file);
					printf("schemas[%d].servers[%d].tls_ciphers = %s\n", i, j, server_login.tls_ciphers);
#endif
				}

				server_login.keep_alive = get_config_int(server_element, "keep_alive");
				server_login.xmlns = get_config_string(server_element, "xml.xmlns");
				server_login.xmlns_xsi = get_config_string(server_element, "xml.xmlns-xsi");
				server_login.xsi_schemaLocation = get_config_string(server_element, "xml.xsi-schemaLocation");

#ifdef COMMENTS
				printf("schemas[%d].servers[%d].keep_alive = %d\n", i, j, server_login.keep_alive);
				printf("schemas[%d].servers[%d].xml.xmlns = %s\n", i, j, server_login.xmlns);
				printf("schemas[%d].servers[%d].xml.xmlns-xsi = %s\n", i, j, server_login.xmlns_xsi);
				printf("schemas[%d].servers[%d].xml.xsi-schemaLocation = %s\n", i, j, server_login.xsi_schemaLocation);
#endif

				/*
				* Loop through logins.
				*/
				struct config_setting_t *conf_logins = config_setting_lookup(server_element, "logins");
				if (conf_logins == NULL)
				{
					fprintf(stdout, "No logins defined for server %d using schema %d\n", j, i);
					continue;
				}
				int login_count = config_setting_length(conf_logins);
#ifdef COMMENTS
				printf("Number of logins for server %d using schema %d: %d\n", j, i, login_count);
#endif

				int k;
				for(k = 0; k < login_count; k++)
				{
					struct config_setting_t *login_element = config_setting_get_elem(conf_logins, k);
					if (login_element == NULL)
					{
						continue;
					}

					struct login_settings login;
					memcpy(&login, &server_login, sizeof(login));
					login.pointer = login_element;

					login.bind_ipv4_mapped = get_config_string(login_element, "bind_ipv4_mapped");
					login.bind_ipv6 = get_config_string(login_element, "bind_ipv6");
					login.ipv4_only = get_config_bool(login_element, "ipv4_only");
					login.ipv6_only = get_config_bool(login_element, "ipv6_only");

#ifdef COMMENTS
					printf("schemas[%d].servers[%d].logins[%d].bind_ipv4_mapped = %s\n", i, j, k, login.bind_ipv4_mapped);
					printf("schemas[%d].servers[%d].logins[%d].ipv4_only = %d\n", i, j, k, login.ipv4_only);
					printf("schemas[%d].servers[%d].logins[%d].bind_ipv6 = %s\n", i, j, k, login.bind_ipv6);
					printf("schemas[%d].servers[%d].logins[%d].ipv6_only = %d\n", i, j, k, login.ipv6_only);
#endif

					login.clID = get_config_string(login_element, "clID");
					login.pw = get_config_string(login_element, "pw");

					login.version = get_config_string(login_element, "options.version");
					login.lang = get_config_string(login_element, "options.lang");

#ifdef COMMENTS
					printf("schemas[%d].servers[%d].logins[%d].clID = %s\n", i, j, k, login.clID);
					printf("schemas[%d].servers[%d].logins[%d].pw = %s\n", i, j, k, login.pw);
#endif

					/*
					* Change login.pw and save to file.
					*/
					/*
					login.pw = "newpassword";
					config_setting_set_string(config_setting_lookup(login.pointer, "pw"), login.pw);
					printf("schemas[%d].servers[%d].logins[%d].pw = %s\n", i, j, k, get_config_string(login_element, "pw"));
					config_write_file(config, CONFIG_FILE);
					*/

#ifdef COMMENTS
					printf("schemas[%d].servers[%d].logins[%d].options.version = %s\n", i, j, k, login.version);
					printf("schemas[%d].servers[%d].logins[%d].options.lang = %s\n", i, j, k, login.lang);
					printf("Schema[%d] pointer: %d\n", i, schema_login.pointer);
					printf("Schema[%d] -> Server[%d] pointer: %d\n", i, j, server_login.pointer);
					printf("Schema[%d] -> Server[%d] -> Login[%d] pointer: %d\n", i, j, k, login.pointer);
#endif

					/*
					* Loop through objURIs.
					*/
					struct config_setting_t *conf_objURIs = config_setting_lookup(login_element, "svcs.objURI");
					if (conf_objURIs == NULL)
					{
						fprintf(stdout, "No objURIs defined for login %d on server %d using schema %d.\n", k, j, i);
						continue;
					}
					int config_objURIs = config_setting_length(conf_objURIs);
#ifdef COMMENTS
					printf("Number of objURIs for login %d on server %d using schema %d: %d\n", k, j, i, config_objURIs);
#endif

					int l;
					for(l = 0; l < config_objURIs; l++)
					{
						struct config_setting_t *objURI_element = config_setting_get_elem(conf_objURIs, l);
						if (objURI_element == NULL)
						{
							continue;
						}
						login.objURI[l] = config_setting_get_string(objURI_element);
#ifdef COMMENTS
						printf("schemas[%d].servers[%d].logins[%d].svcs.objURI[%d] = %s\n", i, j, k, l, login.objURI[l]);
#endif
					}
				}
			}
		}
	}

	config_destroy(config);
	return 0;
}

The *pointer property in the login_settings struct points to the configuration setting (config_setting_t), allowing the configuration config to be updated (without giving the login a pointer to the entire configuration).

I'm not sure if there is a better way of iterating through the configuration file, but the for loops do allow each server to inherit the values from that schema, and each login to inherit the values from that server.

Structs do take up memory, and it is advised to organise the properties in a particular order to optimise memory usage:

  1. Sort by size. That is: shorts before ints before longs, with variable length arrays coming last.
  2. Properties that take up the same amount of space, sort alphabetically.

There are some optimisations I could still make to the login_settings struct. For example, the ints aren't currently in alphabetical order.

This is one of the cases where knowing about C types helps. char* for example, is a variable length character array, but the value of a char* is a pointer to the start of the character array (rather than storing the character array itself). That means all of the char* properties in the struct are, on my system, 8 bytes each.

Likewise an int is 4 bytes on my system. Rather than changing a pointer, the value of an int is stored.

That means for optimal memory usage ints should come before char *s in my struct. *pointer contains a pointer just like all the char* properties, so that can go before or after the char *s.

As for the property *objURI[], that is a dynamic array of character arrays. In other words, an array of pointers (each of which is 8 bytes) and the number of pointers in the array is not yet known. I assume 3 objURIs equal 24 bytes, but I'm unsure how to test the size. All that matters is it is larger than 8 bytes.

So, in alphabetical order the struct becomes the following:

struct login_settings
{
	int enabled;
	int ipv4_only;
	int ipv6_only;
	int keep_alive;
	int port;
	int tls;
	struct config_setting_t *pointer;
	const char *bind_ipv4_mapped;
	const char *bind_ipv6;
	const char *bundle_file;
	const char *clID;
	const char *hostname;
	const char *lang;
	const char *pw;
	const char *tls_ca_file;
	const char *tls_ciphers;
	const char *version;
	const char *xmlns;
	const char *xmlns_xsi;
	const char *xsi_schemaLocation;
	const char *objURI[];
};

I don't think alphabetical order makes any difference on memory usage, I think it is just a style guide recommendation when writing for the Linux kernel. I could probably disregard it for my program, just like I am using Allman-style braces in the if/else statements in my program.

Passing Configuration to Existing Code

I am unsure how best to do this. The old main2 function doesn't take a struct login_settings as a parameter. It uses hard-coded global strings as settings. It also includes command line parameter parsing and that will need to be moved to the new main function.

So I will likely have to rewrite/refactor a large portion of the source code of the program.

As for the login_settings structs (the logins that result in each pass of "Loop through logins") I haven't yet decided how best to use those logins. I can think of two possibilities:

  1. Connect to a server after parsing a single login_settings login.
  2. Store the pointer for each login_settings login in an array, and then loop through the array outside of the loops (i.e. before config_destroy()).

Option 2 seems more logical, as it allows the parsing of the configuration file into logins before iterating through the logins. Option 1 would pause things until (depending on how it is coded) a login is successful or, if badly coded, until a connection is closed.

Option 2 would also allow a way of checking a configuration file is valid (e.g. at present by using the COMMENTS build option and checking every setting is printed to STDOUT) without starting and destroying forks/threads for each login.

What I did was set some variables before looping through the schemas:

	int max_logins = 1;
	struct login_settings *logins[max_logins];
	int logins_count = 0;

	/*
	* Loop through schemas.
	*/

At the end of the login loop I updated those variables:

					if (logins_count < max_logins)
					{
						logins[logins_count] = &login;
						logins_count++;
					}
					else
					{
						fprintf(stderr, "Compiled with only %d maximum logins, configuration file contains at least %d.\n", max_logins, logins_count+1);
						fprintf(stderr, "Please modify 'int max_logins = %d' in source code and recompile.\n", max_logins);
						fprintf(stderr, "NOTE: Program currently only supports 1 schema, 1 server, and 1 login.\n");
						config_destroy(config);
						exit(1);
					}

And then just before calling config_destroy I added a loop to iterate over logins[]:

	int j;
	for (j = 0; j < logins_count; j++)
	{
#ifdef COMMENTS
		printf("logins[%d] pointer: %u\n", j, &logins[j]);
#endif
		// TODO: Do something with logins[j] here.
	}

Fix Multiple Logins

A big issue with the current loops: additional logins overwrite previous ones. I need to fix this.

I have decided to remove the variable length array const char *objURI[] from the login_settings struct. As it is an array of arrays, and I will eventually write the code to add values to it on login (i.e. an EPP server can set some default objURIs) it would probably be best to set the size to the number of objURIs Nominet supports.

It is rather simple to determine how many objURIs Nominet supports as I already have the Nominet schema bundle for XML validation.

ls -1 /home/thejc/Scripts/epp/nom-std-1.0.9-schemas/*.xsd | wc -l
21

Since there are 21 .xsd files, it can be assumed that the current EPP implementation used by Nominet uses 21 namespaces.

Therefore I can replace const char *objURI[] with const char *objURI[21]. With that change every instance of struct login_settings will be the same size. While that increases memory usage, it means I can use malloc without having to use realloc twice.

So, the login_settings struct now looks like this:

struct login_settings
{
	int enabled;
	int ipv4_only;
	int ipv6_only;
	int keep_alive;
	int port;
	int tls;
	struct config_setting_t *pointer;
	const char *bind_ipv4_mapped;
	const char *bind_ipv6;
	const char *bundle_file;
	const char *clID;
	const char *hostname;
	const char *lang;
	const char *pw;
	const char *tls_ca_file;
	const char *tls_ciphers;
	const char *version;
	const char *xmlns;
	const char *xmlns_xsi;
	const char *xsi_schemaLocation;
	const char *objURI[21];
};

And function main now looks like this:

int main(int argc, char **argv)
{
	config = &conf;
	config_init(config);
	int loaded_config = config_read_file(config, CONFIG_FILE);
	if (loaded_config != 1)
	{
		fprintf(stderr, "Error reading config file %s. Error on line %d: %s\n", config_error_file(config), config_error_line(config), config_error_text(config));
		config_destroy(config);
		error("config_read_file");
	}

	int max_logins = 10;
	struct login_settings *logins[max_logins];
	int logins_count = 0;

	/*
	* Loop through schemas.
	*/
	struct config_setting_t conf_schemas;
	struct config_setting_t *config_schemas = &conf_schemas;
	int schema_count = get_root_element_count(config, "schemas", config_schemas);
#ifdef COMMENTS
	printf("Number of schemas: %d\n", schema_count);
#endif

	int i;
	for(i = 0; i < schema_count; i++)
	{
		struct config_setting_t *schema_element = config_setting_get_elem(config_schemas, i);
		if (schema_element== NULL)
		{
			continue;
		}
		struct login_settings schema_login;
		schema_login.bundle_file = get_config_string(schema_element, "bundle_file");
		schema_login.pointer = schema_element;
#ifdef COMMENTS
		printf("schemas[%d].bundle_file = %s\n", i, schema_login.bundle_file);
#endif

		/*
		* Loop through servers.
		*/
		struct config_setting_t conf_servers;
		struct config_setting_t *config_servers = &conf_servers;
		int server_count = get_element_count(schema_element, "servers", config_servers);
		if (config_servers == NULL)
		{
			fprintf(stdout, "No servers defined for schema %d.\n", i);
			continue;
		}
#ifdef COMMENTS
		printf("Number of servers using schema %d: %d\n", i, server_count);
#endif

		int j;
		for(j = 0; j < server_count; j++)
		{
			struct config_setting_t *server_element = config_setting_get_elem(config_servers, j);
			if (server_element == NULL)
			{
				continue;
			}

			struct login_settings server_login;
			memcpy(&server_login, &schema_login, sizeof(server_login));
			server_login.pointer = server_element;

			struct config_setting_t *server_setting = NULL;

			int server_setting_int = get_config_bool(server_element, "enabled");
			if (server_setting_int == 0)
			{
				fprintf(stdout, "Server %d is not enabled.\n", j);
				continue;
			}
			else if (server_setting_int > 0)
			{
				server_login.enabled = server_setting_int;
				server_login.hostname = get_config_string(server_element, "hostname");
				server_login.port = get_config_int(server_element, "port");
#ifdef COMMENTS
				printf("schemas[%d].servers[%d].enabled = %d\n", i, j, server_login.enabled);
				printf("schemas[%d].servers[%d].hostname = %s\n", i, j, server_login.hostname);
				printf("schemas[%d].servers[%d].port = %d\n", i, j, server_login.port);
#endif

				server_setting_int = get_config_bool(server_element, "tls");
				if (server_setting_int == 0)
				{
					fprintf(stdout, "Server %d is not configured for TLS. This program only supports TLS servers.\n", j);
					continue;
				}
				else if (server_setting_int > 0)
				{
					server_login.tls = server_setting_int;
					server_login.tls_ca_file = get_config_string(server_element, "tls_ca_file");
					server_login.tls_ciphers = get_config_string(server_element, "tls_ciphers");
#ifdef COMMENTS
					printf("schemas[%d].servers[%d].tls = %d\n", i, j, server_login.tls);
					printf("schemas[%d].servers[%d].tls_ca_file = %s\n", i, j, server_login.tls_ca_file);
					printf("schemas[%d].servers[%d].tls_ciphers = %s\n", i, j, server_login.tls_ciphers);
#endif
				}

				server_login.keep_alive = get_config_int(server_element, "keep_alive");
				server_login.xmlns = get_config_string(server_element, "xml.xmlns");
				server_login.xmlns_xsi = get_config_string(server_element, "xml.xmlns-xsi");
				server_login.xsi_schemaLocation = get_config_string(server_element, "xml.xsi-schemaLocation");

#ifdef COMMENTS
				printf("schemas[%d].servers[%d].keep_alive = %d\n", i, j, server_login.keep_alive);
				printf("schemas[%d].servers[%d].xml.xmlns = %s\n", i, j, server_login.xmlns);
				printf("schemas[%d].servers[%d].xml.xmlns-xsi = %s\n", i, j, server_login.xmlns_xsi);
				printf("schemas[%d].servers[%d].xml.xsi-schemaLocation = %s\n", i, j, server_login.xsi_schemaLocation);
#endif

				/*
				* Loop through logins.
				*/
				struct config_setting_t *conf_logins = config_setting_lookup(server_element, "logins");
				if (conf_logins == NULL)
				{
					fprintf(stdout, "No logins defined for server %d using schema %d\n", j, i);
					continue;
				}
				int login_count = config_setting_length(conf_logins);
#ifdef COMMENTS
				printf("Number of logins for server %d using schema %d: %d\n", j, i, login_count);
#endif

				int k;
				for(k = 0; k < login_count; k++)
				{
					struct config_setting_t *login_element = config_setting_get_elem(conf_logins, k);
					if (login_element == NULL)
					{
						continue;
					}

					struct login_settings *loginPtr = NULL;
					loginPtr = (struct login_settings *) malloc(sizeof(struct login_settings));
#ifdef COMMENTS
					printf("Pointer loginPtr: %p\n", loginPtr);
#endif
					memcpy(loginPtr, &server_login, sizeof(struct login_settings));
					loginPtr->pointer = login_element;
					loginPtr->bind_ipv4_mapped = get_config_string(login_element, "bind_ipv4_mapped");
					loginPtr->bind_ipv6 = get_config_string(login_element, "bind_ipv6");
					loginPtr->ipv4_only = get_config_bool(login_element, "ipv4_only");
					loginPtr->ipv6_only = get_config_bool(login_element, "ipv6_only");

#ifdef COMMENTS
					printf("schemas[%d].servers[%d].logins[%d].bind_ipv4_mapped = %s\n", i, j, k, loginPtr->bind_ipv4_mapped);
					printf("schemas[%d].servers[%d].logins[%d].ipv4_only = %d\n", i, j, k, loginPtr->ipv4_only);
					printf("schemas[%d].servers[%d].logins[%d].bind_ipv6 = %s\n", i, j, k, loginPtr->bind_ipv6);
					printf("schemas[%d].servers[%d].logins[%d].ipv6_only = %d\n", i, j, k, loginPtr->ipv6_only);
#endif

					loginPtr->clID = get_config_string(login_element, "clID");
					loginPtr->pw = get_config_string(login_element, "pw");

					loginPtr->version = get_config_string(login_element, "options.version");
					loginPtr->lang = get_config_string(login_element, "options.lang");

#ifdef COMMENTS
					printf("schemas[%d].servers[%d].logins[%d].clID = %s\n", i, j, k, loginPtr->clID);
					printf("schemas[%d].servers[%d].logins[%d].pw = %s\n", i, j, k, loginPtr->pw);
#endif

					/*
					* Change login->pw and save to file.
					*/
					/*
					loginPtr->pw = "newpassword";
					config_setting_set_string(config_setting_lookup(loginPtr->pointer, "pw"), loginPtr->pw);
					printf("schemas[%d].servers[%d].logins[%d].pw = %s\n", i, j, k, get_config_string(login_element, "pw"));
					config_write_file(config, CONFIG_FILE);
					*/

#ifdef COMMENTS
					printf("schemas[%d].servers[%d].logins[%d].options.version = %s\n", i, j, k, loginPtr->version);
					printf("schemas[%d].servers[%d].logins[%d].options.lang = %s\n", i, j, k, loginPtr->lang);
					printf("Schema[%d] pointer: %p\n", i, schema_login.pointer);
					printf("Schema[%d] -> Server[%d] pointer: %p\n", i, j, server_login.pointer);
					printf("Schema[%d] -> Server[%d] -> Login[%d] pointer: %p\n", i, j, k, loginPtr->pointer);
#endif

					/*
					* Loop through objURIs.
					*/
					struct config_setting_t *conf_objURIs = config_setting_lookup(login_element, "svcs.objURI");
					if (conf_objURIs == NULL)
					{
						fprintf(stdout, "No objURIs defined for login %d on server %d using schema %d.\n", k, j, i);
					}
					else
					{
						int config_objURIs = config_setting_length(conf_objURIs);
#ifdef COMMENTS
						printf("Number of objURIs for login %d on server %d using schema %d: %d\n", k, j, i, config_objURIs);
#endif

						if (config_objURIs > 21)
						{
							fprintf(stderr, "Maximum objURIs hard-coded to %d but there are %d objURIs for schema[%d].server[%d].login[%d].\n", 21, config_objURIs, k, j, i);
							exit(1);
						}

						int l;
						for(l = 0; l < config_objURIs; l++)
						{
							struct config_setting_t *objURI_element = config_setting_get_elem(conf_objURIs, l);
							if (objURI_element == NULL)
							{
								continue;
							}
							loginPtr->objURI[l] = config_setting_get_string(objURI_element);
#ifdef COMMENTS
							printf("schemas[%d].servers[%d].logins[%d].svcs.objURI[%d] = %s\n", i, j, k, l, loginPtr->objURI[l]);
#endif
						}
					}

					if (logins_count < max_logins)
					{
						logins[logins_count] = loginPtr;
						logins_count++;
					}
					else
					{
						fprintf(stderr, "Compiled with only %d maximum logins, configuration file contains at least %d.\n", max_logins, logins_count+1);
						fprintf(stderr, "Please modify 'int max_logins = %d' in source code and recompile.\n", max_logins);
						fprintf(stderr, "NOTE: Program currently only supports 1 schema, 1 server, and 1 login.\n");
						config_destroy(config);
						exit(1);
					}

				}
			}
		}
	}

	int j;
	for (j = 0; j < logins_count; j++)
	{
		struct login_settings *loginPtr = logins[j];
		struct login_settings login = *loginPtr;
#ifdef COMMENTS
		printf("logins[%d] pointer: %p\n", j, loginPtr);
#endif
		// TODO: Do something with logins[j] here.
	}

	// Cleanup before config_destroy()
	for (j = 0; j < logins_count; j++)
	{
		struct login_settings *loginPtr = logins[j];
		free(loginPtr);
	}

	config_destroy(config);
	return 0;
}

I have also modified two functions to fix a segmentation fault that occurs if the configuration file contains an empty schema/server/login:

/*
* Function get_element_count:
*	* Returns number (int) of elements in list 'name' in configuration 'config'.
*	* Updates pointer '*config_element' to point to element 'name'.
*
* config_t *config : pointer to parsed config
* char *name : pointer to name of element
* config_setting_t *conf_element : pointer to element
*/
int get_root_element_count(config_t *config, char *name, config_setting_t *config_element)
{
	config_setting_t *conf_element = config_lookup(config, name);
	if (conf_element == NULL)
	{
		fprintf(stderr, "No %s found in configuration file.\n", name);
		exit(1);
	}
	else
	{
		*config_element = *conf_element;
		return config_setting_length(config_element);
	}
}

/*
* Function get_element_count:
*	* Returns number (int) of elements in list 'name' in configuration setting 'config_setting'.
*	* Updates pointer '*config_element' to point to element 'name'.
*
* config_setting_t *config : pointer to parsed config setting
* char *name : pointer to name of element
* config_setting_t *conf_element : pointer to element
*/
int get_element_count(config_setting_t *config_setting, char *name, config_setting_t *config_element)
{
	config_setting_t *conf_element = config_setting_lookup(config_setting, name);
	if (conf_element == NULL)
	{
		fprintf(stderr, "No %s found in configuration file for this schema.\n", name);
		return 0;
	}
	else {
		*config_element = *conf_element;
		return config_setting_length(config_element);
	

Refactoring the Code

My main and main2 functions became too long and unwieldy. I decided to split everything into smaller functions where it made sense, and there is probably still some work I could do on that.

The configuration file is now fully iterated over, however I have set a hard limit of 1 login until I can work out how best to deal with the socket blocking.

The configuration file implementation works, the TCP and TLS part of the program doesn't. Since this article was not meant to focus on anything other than parsing configuration files I have decided to leave this article with code that can create a connection to Nominet's EPP server and send a login, but cannot do anything else (including logout).

There are numerous ways I could move on from here:

  • Learn about threading (config needs to be maintained across connections for file updating) and create a new thread for each connection.
  • Incorporate parts of tcp-server.c so that the EPP header gets parsed and the socket isn't infinitely blocked.

The EPP protocol does make things easier as it is, with the exception of pipelining, a symmetric protocol. Which end is next to send data is defined.

  1. Client connects to server.
  2. Server sends 4 byte "how much to read" EPP header.
  3. Server sends greeting message.
  4. Client sends 4 byte EPP header.
  5. Client sends login message.
  6. Server sends 4 byte EPP header.
  7. Server sends login message response.
  8. Client sends 4 byte EPP header.
  9. Client sends command.
  10. Server sends 4 byte EPP header.
  11. Server sends command response.
  12. Client sends 4 byte EPP header.
  13. Client sends command.
  14. Server sends 4 byte EPP header.
  15. Server sends command response.
  16. Client sends 4 byte EPP header.
  17. Client sends logout command.
  18. Server sends 4 byte EPP header.
  19. Server sends logout response.
  20. Server closes connection.

So here is the current source code of connection.c, which has changed a lot since I last posted it. It is nowhere near close to being an EPP client, however.

/* Standard Libraries */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <getopt.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>
/* libconfig */
#include <libconfig.h>

#define config_setting_lookup config_lookup_from

/*
* Build (Debian Jessie):
*	* gcc -o connection connection.c -lgnutls -lconfig
* Build (Debian Jessie) with more verbose comments in output:
*	* gcc -o connection connection.c -lgnutls -lconfig -DCOMMENTS
*/

/*
* Global Variables:
*	* LOG_LEVEL: integer between 0 and 9 for setting GnuTLS log level.
*		Set with --gnutls_log_level <level>.
*	* CONFIG_FILE: string containing full path to configuration file.
*		Set with --config <file>.
*/
int LOG_LEVEL = 0;
char *CONFIG_FILE = "";

// Set a maximum number of logins.
int max_logins = 1;
// Create *logins[] - a fixed size array of 1 pointer.
struct login_settings *logins[1];
// logins_iterate() will increase logins_count after each login is parsed.
int logins_count = 0;

struct login_settings
{
	int enabled;
	int ipv4_only;
	int ipv6_only;
	int keep_alive;
	int tls;
	int objURIs;
	int *connectionPtr;
	gnutls_session_t *gnutls_sessionPtr;
	gnutls_certificate_credentials_t x509_cred;
	struct config_setting_t *pointer;
	const char *bind_ipv4_mapped;
	const char *bind_ipv6;
	const char *bundle_file;
	const char *clID;
	const char *hostname;
	const char *lang;
	const char *port;
	const char *pw;
	const char *tls_ca_file;
	const char *tls_ciphers;
	const char *version;
	const char *xmlns;
	const char *xmlns_xsi;
	const char *xsi_schemaLocation;
	const char *objURI[21];
};

struct config_t conf;
struct config_t *config;

void error_exit(const char *msg);

int command_options(int argc, char **argv);
void open_config();
void create_connections();
void close_connections();
void close_connection(struct login_settings login);
void close_config();

int get_root_element_count(config_t *config, char *name, config_setting_t *config_element);
int get_element_count(config_setting_t *config, char *name, config_setting_t *config_element);
int get_config_int(config_setting_t *setting, char *name);
int get_config_bool(config_setting_t *setting, char *name);
const char *get_config_string(config_setting_t *setting, char *name);

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 tls_connection(struct login_settings login);
int make_one_connection(struct login_settings login, const char *address, int port);
int get_ip_from_hostname(struct login_settings login, char *, char *);
int verify_cert(struct gnutls_session_int *);

void schemas_iterate();
void servers_iterate(int schemaInt, struct login_settings schema_login, struct config_setting_t *schema_element);
void logins_iterate(int schemaInt, struct login_settings schema_login, struct config_setting_t *schema_element, int serverInt, struct login_settings server_login, struct config_setting_t *server_element);

int main(int argc, char **argv)
{
	// Parse command line options.
	command_options(argc, argv);

	// Open configuration file into config.
	open_config();

	/*
	* Iterate through configuration file recursively via schemas_iterate(), servers_iterate(), and logins_iterate().
	* logins_iterate() populates *logins[].
	*/
	schemas_iterate();

	// Iterate through *logins[] and create connections.
	create_connections();

	// Iterate through *logins[] and close connections.
//	close_connections();

	// Free logins and destroy config.
	close_config();

	return 0;
}

int command_options(int argc, char **argv)
{
/* Handle command line parameters */
	int c;
	while (1)
	{
		/*
		* Values returned are char, so start at 1001 for long options without short
		*  equivalent so they don't interfere with short options (e.g. 'z' = 122).
		* If a decision is made to later add a short option, change the number in
		*  the array and the case statement for that option (e.g. replacing 1008 with '4'.
		*/
		static struct option long_options[] =
		{
			{"gnutls_log_level", required_argument, 0, 1001},
			{"config", required_argument, 0, 1002},
			{"help", no_argument, 0, 'h'},
			{0, 0, 0, 0}
		};

		int option_index = 0;
		c = getopt_long(argc, argv, "h", long_options, &option_index);
		if (c == -1)
		{
			break;
		}

		switch (c)
		{
			case 0:
				/* If this option set a flag, do nothing else now. */
				if (long_options[option_index].flag != 0)
				{
					break;
				}
				break;
			case 1001:
				LOG_LEVEL = atoi(optarg);
				break;
			case 1002:
				CONFIG_FILE = optarg;
				break;
			case 'h':
				printf("Usage: %s [options]\n",argv[0]);
				printf("Options:\n");
				printf("  --config <file>\n");
				printf("	Path to configuration file.\n");
				printf("  --gnutls_log_level <value>\n");
				printf("	Whole number between 0 and 9 for setting GnuTLS log level.\n");
				printf("	Default value: 0\n");
				printf("  --help\n");
				printf("	Display this information.\n");
				exit(0);
				break;
			default:
				abort();
		}

	}
}

void open_config()
{
	if (strlen(CONFIG_FILE) == 0)
	{
		fprintf(stderr, "No configuration file specified.\n");
		exit(1);
	}

	config = &conf;
	config_init(config);

	int loaded_config = config_read_file(config, CONFIG_FILE);
	if (loaded_config != 1)
	{
		fprintf(stderr, "Error reading config file %s. Error on line %d: %s\n", config_error_file(config), config_error_line(config), config_error_text(config));
		config_destroy(config);
	}
}

void schemas_iterate(struct login_settings *logins[], int *logins_count, int max_logins)
{
	/*
	* Loop through schemas.
	*/
	struct config_setting_t conf_schemas;
	struct config_setting_t *config_schemas = &conf_schemas;
	int schema_count = get_root_element_count(config, "schemas", config_schemas);
#ifdef COMMENTS
	printf("Number of schemas: %d\n", schema_count);
#endif

	int schemaInt;
	for(schemaInt = 0; schemaInt < schema_count; schemaInt++)
	{
		struct config_setting_t *schema_element = config_setting_get_elem(config_schemas, schemaInt);
		if (schema_element== NULL)
		{
			continue;
		}
		struct login_settings schema_login;
		schema_login.bundle_file = get_config_string(schema_element, "bundle_file");
		schema_login.pointer = schema_element;
#ifdef COMMENTS
		printf("schemas[%d].bundle_file = %s\n", schemaInt, schema_login.bundle_file);
#endif
		servers_iterate(schemaInt, schema_login, schema_element);
	}
}

void servers_iterate(int schemaInt, struct login_settings schema_login, struct config_setting_t *schema_element)
{
	/*
	* Loop through servers.
	*/
	struct config_setting_t conf_servers;
	struct config_setting_t *config_servers = &conf_servers;
	int server_count = get_element_count(schema_element, "servers", config_servers);
	if (config_servers == NULL)
	{
		fprintf(stdout, "No servers defined for schema %d.\n", schemaInt);
		return;
	}
#ifdef COMMENTS
	printf("Number of servers using schema %d: %d\n", schemaInt, server_count);
#endif

	int serverInt;
	for(serverInt = 0; serverInt < server_count; serverInt++)
	{
		struct config_setting_t *server_element = config_setting_get_elem(config_servers, serverInt);
		if (server_element == NULL)
		{
			continue;
		}

		struct login_settings server_login;
		memcpy(&server_login, &schema_login, sizeof(server_login));
		server_login.pointer = server_element;

		struct config_setting_t *server_setting = NULL;

		int server_setting_int = get_config_bool(server_element, "enabled");
		if (server_setting_int == 0)
		{
			fprintf(stdout, "Server %d is not enabled.\n", serverInt);
			continue;
		}
		else if (server_setting_int > 0)
		{
			server_login.enabled = server_setting_int;
			server_login.hostname = get_config_string(server_element, "hostname");
			server_login.port = get_config_string(server_element, "port");
#ifdef COMMENTS
			printf("schemas[%d].servers[%d].enabled = %d\n", schemaInt, serverInt, server_login.enabled);
			printf("schemas[%d].servers[%d].hostname = %s\n", schemaInt, serverInt, server_login.hostname);
			printf("schemas[%d].servers[%d].port = %d\n", schemaInt, serverInt, server_login.port);
#endif

			server_setting_int = get_config_bool(server_element, "tls");
			if (server_setting_int == 0)
			{
				fprintf(stdout, "Server %d is not configured for TLS. This program only supports TLS servers.\n", serverInt);
				continue;
			}
			else if (server_setting_int > 0)
			{
				server_login.tls = server_setting_int;
				server_login.tls_ca_file = get_config_string(server_element, "tls_ca_file");
				server_login.tls_ciphers = get_config_string(server_element, "tls_ciphers");
#ifdef COMMENTS
				printf("schemas[%d].servers[%d].tls = %d\n", schemaInt, serverInt, server_login.tls);
				printf("schemas[%d].servers[%d].tls_ca_file = %s\n", schemaInt, serverInt, server_login.tls_ca_file);
				printf("schemas[%d].servers[%d].tls_ciphers = %s\n", schemaInt, serverInt, server_login.tls_ciphers);
#endif
			}

			server_login.keep_alive = get_config_int(server_element, "keep_alive");
			server_login.xmlns = get_config_string(server_element, "xml.xmlns");
			server_login.xmlns_xsi = get_config_string(server_element, "xml.xmlns-xsi");
			server_login.xsi_schemaLocation = get_config_string(server_element, "xml.xsi-schemaLocation");

#ifdef COMMENTS
			printf("schemas[%d].servers[%d].keep_alive = %d\n", schemaInt, serverInt, server_login.keep_alive);
			printf("schemas[%d].servers[%d].xml.xmlns = %s\n", schemaInt, serverInt, server_login.xmlns);
			printf("schemas[%d].servers[%d].xml.xmlns-xsi = %s\n", schemaInt, serverInt, server_login.xmlns_xsi);
			printf("schemas[%d].servers[%d].xml.xsi-schemaLocation = %s\n", schemaInt, serverInt, server_login.xsi_schemaLocation);
#endif
			logins_iterate(schemaInt, schema_login, schema_element, serverInt, server_login, server_element);
		}
	}
}

void logins_iterate(int schemaInt, struct login_settings schema_login, struct config_setting_t *schema_element, int serverInt, struct login_settings server_login, struct config_setting_t *server_element)
{
	/*
	* Loop through logins.
	*/

	struct config_setting_t *conf_logins = config_setting_lookup(server_element, "logins");
	if (conf_logins == NULL)
	{
		fprintf(stdout, "No logins defined for server %d using schema %d\n", serverInt, schemaInt);
		return;
	}
	int login_count = config_setting_length(conf_logins);
#ifdef COMMENTS
	printf("Number of logins for server %d using schema %d: %d\n", serverInt, schemaInt, login_count);
#endif

	int loginInt;
	for(loginInt = 0; loginInt < login_count; loginInt++)
	{
		struct config_setting_t *login_element = config_setting_get_elem(conf_logins, loginInt);
		if (login_element == NULL)
		{
			continue;
		}

		struct login_settings *loginPtr = NULL;
		loginPtr = (struct login_settings *) malloc(sizeof(struct login_settings));
#ifdef COMMENTS
		printf("Pointer loginPtr: %p\n", loginPtr);
#endif
		memcpy(loginPtr, &server_login, sizeof(struct login_settings));
		loginPtr->pointer = login_element;
		loginPtr->bind_ipv4_mapped = get_config_string(login_element, "bind_ipv4_mapped");
		loginPtr->bind_ipv6 = get_config_string(login_element, "bind_ipv6");
		loginPtr->ipv4_only = get_config_bool(login_element, "ipv4_only");
		loginPtr->ipv6_only = get_config_bool(login_element, "ipv6_only");

#ifdef COMMENTS
		printf("schemas[%d].servers[%d].logins[%d].bind_ipv4_mapped = %s\n", schemaInt, serverInt, loginInt, loginPtr->bind_ipv4_mapped);
		printf("schemas[%d].servers[%d].logins[%d].ipv4_only = %d\n", schemaInt, serverInt, loginInt, loginPtr->ipv4_only);
		printf("schemas[%d].servers[%d].logins[%d].bind_ipv6 = %s\n", schemaInt, serverInt, loginInt, loginPtr->bind_ipv6);
		printf("schemas[%d].servers[%d].logins[%d].ipv6_only = %d\n", schemaInt, serverInt, loginInt, loginPtr->ipv6_only);
#endif

		loginPtr->clID = get_config_string(login_element, "clID");
		loginPtr->pw = get_config_string(login_element, "pw");

		loginPtr->version = get_config_string(login_element, "options.version");
		loginPtr->lang = get_config_string(login_element, "options.lang");

#ifdef COMMENTS
		printf("schemas[%d].servers[%d].logins[%d].clID = %s\n", schemaInt, serverInt, loginInt, loginPtr->clID);
		printf("schemas[%d].servers[%d].logins[%d].pw = %s\n", schemaInt, serverInt, loginInt, loginPtr->pw);
#endif

		/*
		* Change login->pw and save to file.
		*/
		/*
		loginPtr->pw = "newpassword";
		config_setting_set_string(config_setting_lookup(loginPtr->pointer, "pw"), loginPtr->pw);
		printf("schemas[%d].servers[%d].logins[%d].pw = %s\n", schemaInt, serverInt, loginInt, get_config_string(login_element, "pw"));
		config_write_file(config, CONFIG_FILE);
		*/

#ifdef COMMENTS
		printf("schemas[%d].servers[%d].logins[%d].options.version = %s\n", schemaInt, serverInt, loginInt, loginPtr->version);
		printf("schemas[%d].servers[%d].logins[%d].options.lang = %s\n", schemaInt, serverInt, loginInt, loginPtr->lang);
		printf("Schema[%d] pointer: %p\n", schemaInt, schema_login.pointer);
		printf("Schema[%d] -> Server[%d] pointer: %p\n", schemaInt, serverInt, server_login.pointer);
		printf("Schema[%d] -> Server[%d] -> Login[%d] pointer: %p\n", schemaInt, serverInt, loginInt, loginPtr->pointer);
#endif

		/*
		* Loop through objURIs.
		*/
		struct config_setting_t *conf_objURIs = config_setting_lookup(login_element, "svcs.objURI");
		if (conf_objURIs == NULL)
		{
			fprintf(stdout, "No objURIs defined for login %d on server %d using schema %d.\n", loginInt, serverInt, schemaInt);
		}
		else
		{
			int config_objURIs = config_setting_length(conf_objURIs);
#ifdef COMMENTS
			printf("Number of objURIs for login %d on server %d using schema %d: %d\n", loginInt, serverInt, schemaInt, config_objURIs);
#endif

			if (config_objURIs > 21)
			{
				fprintf(stderr, "Maximum objURIs hard-coded to %d but there are %d objURIs for schema[%d].server[%d].login[%d].\n", 21, config_objURIs, loginInt, serverInt, schemaInt);
				exit(1);
			}
			loginPtr->objURIs = config_objURIs;

			int l;
			for(l = 0; l < config_objURIs; l++)
			{
				struct config_setting_t *objURI_element = config_setting_get_elem(conf_objURIs, l);
				if (objURI_element == NULL)
				{
					continue;
				}
				loginPtr->objURI[l] = config_setting_get_string(objURI_element);
#ifdef COMMENTS
				printf("schemas[%d].servers[%d].logins[%d].svcs.objURI[%d] = %s\n", schemaInt, serverInt, loginInt, l, loginPtr->objURI[l]);
#endif
			}
		}

		if (logins_count < max_logins)
		{
			logins[logins_count] = loginPtr;
			logins_count++;
		}
		else
		{
			fprintf(stderr, "Compiled with only %d maximum logins, configuration file contains at least %d.\n", max_logins, logins_count+1);
			fprintf(stderr, "Please modify 'int max_logins = %d' in source code and recompile.\n", max_logins);
			fprintf(stderr, "NOTE: Program currently only supports 1 schema, 1 server, and 1 login.\n");
			config_destroy(config);
			exit(1);
		}
	}
}

void create_connections()
{
	int i;
	// Create connection per login.
	for (i = 0; i < logins_count; i++)
	{
		struct login_settings *loginPtr = logins[i];
		struct login_settings login = *loginPtr;
#ifdef COMMENTS
		printf("logins[%d] pointer: %p\n", i, loginPtr);
#endif

		tls_connection(login);
	}
}

void close_connections()
{
	int i;
	// Close connection per login.
	for (i = 0; i < logins_count; i++)
	{
		struct login_settings *loginPtr = logins[i];
		struct login_settings login = *loginPtr;
		close_connection(login);
	}
}

void close_connection(struct login_settings login)
{
	gnutls_session_t *gnutls_sessionPtr = login.gnutls_sessionPtr;
	gnutls_session_t session = *gnutls_sessionPtr;
	int *connfdPtr = login.connectionPtr;
	int connfd = *connfdPtr;

	char logoutString[4096];
	int n = sprintf(logoutString, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<epp xmlns=\"%s\" xmlns:xsi=\"%s\" xsi:schemaLocation=\"%s\">\n\t<command>\n\t\t<logout />\n\t</command>\n</epp>\n", login.xmlns, login.xmlns_xsi, login.xsi_schemaLocation);
	printf("%s\n",logoutString);
	gnutls_record_send(session, logoutString, strlen(logoutString));

	gnutls_bye(session, GNUTLS_SHUT_RDWR);
	gnutls_deinit(session);
	login.gnutls_sessionPtr = NULL;
	close(connfd);
	free(connfdPtr);
	login.connectionPtr = NULL;
	gnutls_certificate_free_credentials(login.x509_cred);
	gnutls_global_deinit();
}

void close_config()
{
	int i;
	// Cleanup before config_destroy()
	for (i = 0; i < logins_count; i++)
	{
		struct login_settings *loginPtr = logins[i];
		free(loginPtr);
	}

	config_destroy(config);
}

int tls_connection(struct login_settings login)
{

	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);
	login.x509_cred = x509_cred;
	gnutls_certificate_set_x509_trust_file(x509_cred, login.tls_ca_file, GNUTLS_X509_FMT_PEM);
	printf("CA file: %s\n", login.tls_ca_file);

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

	gnutls_session_set_ptr(session, (void *) login.hostname);
	gnutls_server_name_set(session, GNUTLS_NAME_DNS, login.hostname, strlen(login.hostname));

	const char *error = NULL;
	res = gnutls_priority_set_direct(session, login.tls_ciphers, &error);
	if (res != GNUTLS_E_SUCCESS)
	{
		fprintf(stderr, "Invalid Cipher: %s\n",error);
		fprintf(stderr, "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 = (void *) login.hostname;
	char ip[INET6_ADDRSTRLEN];
	get_ip_from_hostname(login, hostname, ip);
#ifdef COMMENTS
	printf("%s resolved to %s\n", hostname, ip);
	char loginString[4096];
	int n = sprintf(loginString, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<epp xmlns=\"%s\" xmlns:xsi=\"%s\" xsi:schemaLocation=\"%s\">\n\t<command>\n\t\t<login>\n\t\t\t<clID>%s</clID>\n\t\t\t<pw>%s</pw>\n\t\t\t<options>\n\t\t\t\t<version>%s</version>\n\t\t\t\t<lang>%s</lang>\n\t\t\t</options>\n\t\t\t<svcs>\n", login.xmlns, login.xmlns_xsi, login.xsi_schemaLocation, login.clID, login.pw, login.version, login.lang);
	int i;
	for (i = 0; i < login.objURIs; i++)
	{
		char objURI[1024];
		int o = sprintf(objURI, "\t\t\t\t<objURI>%s</objURI>\n", login.objURI[i]);
		n = n+o;
		strcat(loginString, objURI);
	}
	strcat(loginString, "\t\t\t</svcs>\n\t\t</login>\n\t</command>\n</epp>\n");
#endif

	int connfd = make_one_connection(login, ip, atoi(login.port));

	int *connfdPtr = malloc(sizeof(int));
	*connfdPtr = connfd;
	login.connectionPtr = connfdPtr;
	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))
	{
		fprintf(stderr, "Error code %d in gnutls_handshake(): %s\n",res,gnutls_strerror(res));
		exit(1);
	}


#ifdef COMMENTS
	printf("∨∨∨---From Client---∨∨∨\n");
#endif
	printf("%s\n",loginString);
	gnutls_record_send(session, loginString, strlen(loginString));
#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)
		{
			fprintf(stderr, "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))
		{
			fprintf(stderr, "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

		close_connection(login);

#ifdef COMMENTS
	printf("All done!\n");
#endif

	return 0;
}

/*
* Function get_root_element_count:
*	* Returns number (int) of elements in list 'name' in configuration 'config'.
*	* Updates pointer '*config_element' to point to element 'name'.
*
* config_t *config : pointer to parsed config
* char *name : pointer to name of element
* config_setting_t *conf_element : pointer to element
*/
int get_root_element_count(config_t *config, char *name, config_setting_t *config_element)
{
	config_setting_t *conf_element = config_lookup(config, name);
	if (conf_element == NULL)
	{
		fprintf(stderr, "No %s found in configuration file.\n", name);
		exit(1);
	}
	else
	{
		*config_element = *conf_element;
		return config_setting_length(config_element);
	}
}

/*
* Function get_element_count:
*	* Returns number (int) of elements in list 'name' in configuration setting 'config_setting'.
*	* Updates pointer '*config_element' to point to element 'name'.
*
* config_setting_t *config : pointer to parsed config setting
* char *name : pointer to name of element
* config_setting_t *conf_element : pointer to element
*/
int get_element_count(config_setting_t *config_setting, char *name, config_setting_t *config_element)
{
	config_setting_t *conf_element = config_setting_lookup(config_setting, name);
	if (conf_element == NULL)
	{
		fprintf(stderr, "No %s found in configuration file for this schema.\n", name);
		return 0;
	}
	else {
		*config_element = *conf_element;
		return config_setting_length(config_element);
	}
}

/*
* Function get_config_int looks up the integer value of 'name'
*  in the configuration setting 'setting' and returns the integer.
* -1 is returned if 'name' does not exist.
*/
int get_config_int(config_setting_t *setting, char *name)
{
	config_setting_t *setting_pointer = NULL;
	setting_pointer = config_setting_lookup(setting, name);
	if (setting_pointer != NULL)
	{
		return config_setting_get_int(setting_pointer);
	}
	else
	{
		return -1;
	}
}

/*
* Function get_config_bool looks up the boolean value of 'name'
*  in the configuration setting 'setting' and returns it as an integer.
* -1 is returned if 'name' does not exist.
*/
int get_config_bool(config_setting_t *setting, char *name)
{
	config_setting_t *setting_pointer = NULL;
	setting_pointer = config_setting_lookup(setting, name);
	if (setting_pointer != NULL)
	{
		return config_setting_get_bool(setting_pointer);
	}
	else
	{
		return -1;
	}
}

/*
* Function get_config_string looks up the string value of 'name'
*  in the configuration setting 'setting' and returns the string.
* NULL is returned if 'name' does not exist.
*/
const char *get_config_string(config_setting_t *setting, char *name)
{
	config_setting_t *setting_pointer = NULL;
	setting_pointer = config_setting_lookup(setting, name);
	if (setting_pointer != NULL)
	{
		return config_setting_get_string(setting_pointer);
	}
	else
	{
		return NULL;
	}
}

/*
* function get_ip_from_hostname 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 get_ip_from_hostname(struct login_settings login, char *hostname, char *ip)
{
	struct addrinfo * _addrinfo;
	struct addrinfo * _res;
	int errorcode = 0;
	static int ip_found = 0;

	if (login.ipv4_only == 1 && login.ipv6_only == 1)
	{
		fprintf(stderr, "get_ip_from_hostname error: both IPv4 and IPv6 is disabled.\n");
		exit(1);
	}

	errorcode = getaddrinfo(hostname, login.port, NULL, &_addrinfo);
	if (errorcode != 0)
	{
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(errorcode));
		exit(1);
	}

	for (_res = _addrinfo; _res != NULL; _res = _res->ai_next)
	{
		if (_res->ai_family == AF_INET && login.ipv6_only != 1)
		{
			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(strlen(ipv4_mapping) + strlen(ip) + 1);
				strcpy(ipv4_mapped, ipv4_mapping);
				strcat(ipv4_mapped, ip);
				strcpy(ip, ipv4_mapped);
				free(ipv4_mapped);
				ip_found = 1;
				return 0;
			}
		}
		else if (_res->ai_family == AF_INET6 && login.ipv4_only != 1)
		{
			if (NULL == inet_ntop(AF_INET6, &((struct sockaddr_in6 *)_res->ai_addr)->sin6_addr, ip, INET6_ADDRSTRLEN))
			{
				perror("inet_ntop");
				exit(1);
			}
			else
			{
				ip_found = 1;
				return 0;
			}
		}
	}

	if (ip_found == 0)
	{
		if (login.ipv4_only == 1)
		{
			fprintf(stderr, "Hostname %s only has IPv6 IP address(es).\n", login.hostname);
			fprintf(stderr,  "Unable to connect due to running in IPv4-only mode.\n");
		}
		else if (login.ipv6_only == 1)
		{
			fprintf(stderr, "Hostname %s only has IPv4 IP address(es).\n", login.hostname);
			fprintf(stderr, "Unable to connect due to running in IPv6-only mode.\n");
		}
		exit(1);
	}

	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)
{
	fprintf(stderr, "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(struct login_settings login, 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 = (void *) login.bind_ipv6;
	size_t len = strlen(ip);
	size_t spn = strcspn(ip, ".");
	if (spn != len)
	{
		local_bind_address = (void *) login.bind_ipv4_mapped;
	}
	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)
	{
		perror("bind");
		exit(1);
	}

	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)
	{
		perror("connect");
		exit(1);
	}
	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)
	{
		fprintf(stderr, "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)
	{
		fprintf(stderr, "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;
}