|
@@ -1,3 +1,5 @@
|
|
|
+#define _POSIX_SOURCE
|
|
|
+
|
|
|
#include <stdio.h>
|
|
|
#include <stdlib.h>
|
|
|
#include <strings.h>
|
|
@@ -8,26 +10,69 @@
|
|
|
#include <sys/socket.h>
|
|
|
#include <netdb.h>
|
|
|
|
|
|
+#include <openssl/x509.h>
|
|
|
+#include <openssl/ssl.h>
|
|
|
+
|
|
|
#include "qa.h"
|
|
|
|
|
|
#define SOCKET_PROTOCOL 0
|
|
|
#define INVALID_SOCKET (-1)
|
|
|
|
|
|
-int init_client(const struct qa_conf *options)
|
|
|
+/**
|
|
|
+ * \brief Converts a uri into a tuple {host, service}.
|
|
|
+ *
|
|
|
+ * Parses an input string containing a host and (maybe) a service/port.
|
|
|
+ * Valid options are:
|
|
|
+ * - service://hostname
|
|
|
+ * - hostname
|
|
|
+ * - hostname:port
|
|
|
+ * The resulting tuple will be stored in params \ref host and \ref service.
|
|
|
+ * \note \ref uri might be modified during parsing.
|
|
|
+ *
|
|
|
+ * \param[in] uri The input uri.
|
|
|
+ * \param[out] host Place where to store parsed host.
|
|
|
+ * \param[out] service Place where to store parsed service.
|
|
|
+ *
|
|
|
+ * \return 0 if the string was not parsable, 1 otherwise.
|
|
|
+
|
|
|
+ */
|
|
|
+int host_port(char *uri, char **host, char **service)
|
|
|
+{
|
|
|
+ char* c;
|
|
|
+
|
|
|
+ if (!(c = strchr(uri, ':'))) {
|
|
|
+ *host = uri;
|
|
|
+ *service = NULL;
|
|
|
+ } else {
|
|
|
+ *c = '\0';
|
|
|
+ if (c[1] == '/' && c[2] == '/') {
|
|
|
+ *service = uri;
|
|
|
+ *host = c+3;
|
|
|
+ } else {
|
|
|
+ *service = c+1;
|
|
|
+ *host = uri;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return 1;
|
|
|
+}
|
|
|
+
|
|
|
+int init_client(const char *host, const char *port)
|
|
|
{
|
|
|
struct addrinfo hints;
|
|
|
struct addrinfo *result, *rp;
|
|
|
int s;
|
|
|
int i;
|
|
|
|
|
|
+
|
|
|
hints.ai_family = AF_UNSPEC;
|
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
|
hints.ai_flags = 0;
|
|
|
hints.ai_protocol = 0;
|
|
|
|
|
|
- if ((i=getaddrinfo(options->host, options->port, NULL, &result))) {
|
|
|
- fprintf(stderr, "Error: %s\n", gai_strerror(i));
|
|
|
- exit(EXIT_FAILURE);
|
|
|
+ if ((i=getaddrinfo(host, port, NULL, &result))) {
|
|
|
+ BIO_printf(bio_err, "Error: %s\n", gai_strerror(i));
|
|
|
+ return -1;
|
|
|
}
|
|
|
|
|
|
for (rp=result; rp; rp = rp->ai_next) {
|
|
@@ -44,7 +89,114 @@ int init_client(const struct qa_conf *options)
|
|
|
|
|
|
if (connect(s, rp->ai_addr, rp->ai_addrlen) != -1) break;
|
|
|
}
|
|
|
- if (!rp) exit(EXIT_FAILURE);
|
|
|
+ if (!rp) return -1;
|
|
|
|
|
|
return s;
|
|
|
}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief Connection informations.
|
|
|
+ */
|
|
|
+typedef struct qa_connection {
|
|
|
+ int socket; /**< socket file descriptor. */
|
|
|
+ SSL* ssl; /**< ssl handler for this connection. */
|
|
|
+ SSL_CTX* ctx; /**< ssl context used in this connection. */
|
|
|
+} qa_connection_t;
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief Destructor for a \ref qa_connection.
|
|
|
+ *
|
|
|
+ * Closes the socket, shuts down the connection, and frees all memory used for
|
|
|
+ * holding the connection.
|
|
|
+ * \note Input might be partial (ex. a socket exists, but not no ssl session).
|
|
|
+ *
|
|
|
+ * \param c The connection to be freed.
|
|
|
+ */
|
|
|
+static void qa_connection_free(struct qa_connection* c)
|
|
|
+{
|
|
|
+ if (!c) return;
|
|
|
+
|
|
|
+ if (c->socket != -1)
|
|
|
+ close(c->socket);
|
|
|
+ if (c->ssl) {
|
|
|
+ SSL_shutdown(c->ssl);
|
|
|
+ SSL_free(c->ssl);
|
|
|
+ }
|
|
|
+ if (c->ctx)
|
|
|
+ SSL_CTX_free(c->ctx);
|
|
|
+
|
|
|
+ free(c);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+static int verify_callback(int ok, X509_STORE_CTX* ctx)
|
|
|
+{
|
|
|
+ return ok;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief Set up a new ssl connection.
|
|
|
+ *
|
|
|
+ * Create a new \ref qa_connection, turning on OpenSSL (if not yet started), and
|
|
|
+ * opening a socket with the target server over ssl.
|
|
|
+ *
|
|
|
+ * \param[in] conf Configuration holding informations about the target.
|
|
|
+ * \return The new connection.
|
|
|
+ */
|
|
|
+struct qa_connection* qa_connection_new(char* address)
|
|
|
+{
|
|
|
+ struct qa_connection* c;
|
|
|
+ char *host, *port;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ /* parse input address */
|
|
|
+ if (!host_port(address, &host, &port)) goto error;
|
|
|
+
|
|
|
+ c = malloc(sizeof(struct qa_connection));
|
|
|
+ if (!c) goto error;
|
|
|
+ /* set up context, and protocol versions */
|
|
|
+ c->ctx = SSL_CTX_new(SSLv23_client_method());
|
|
|
+ if (!c->ctx) goto error;
|
|
|
+ /* create the ssl session, disabling certificate verification */
|
|
|
+ SSL_CTX_set_verify(c->ctx, SSL_VERIFY_NONE, verify_callback);
|
|
|
+ c->ssl = SSL_new(c->ctx);
|
|
|
+ if (!c->ssl) goto error;
|
|
|
+ /* open the socket over ssl */
|
|
|
+ c->socket = init_client(host, port);
|
|
|
+ if (c->socket == -1) goto error;
|
|
|
+ if (!SSL_set_fd(c->ssl, c->socket)) goto error;
|
|
|
+ if (SSL_connect(c->ssl) != 1) goto error;
|
|
|
+
|
|
|
+ SSL_set_connect_state(c->ssl);
|
|
|
+ return c;
|
|
|
+
|
|
|
+ error:
|
|
|
+ /* XXX. add checks for errno, and the ssl error stack (ssl_get_error) */
|
|
|
+ qa_connection_free(c);
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * \brief Fetches the certificate opening a tcp connection to the given address.
|
|
|
+ *
|
|
|
+ * Attempts to open a new tcp connection to the address `address`, and return
|
|
|
+ * the X509 certificate presented by the server.
|
|
|
+ *
|
|
|
+ * \param address[in] the uri to which handshake a ssl connection.
|
|
|
+ * \return a valid pointer to the X509 certificate if the handshake succeeded,
|
|
|
+ * NULL otherwise.
|
|
|
+ */
|
|
|
+X509* get_remote_cert(char *address)
|
|
|
+{
|
|
|
+ X509 *crt;
|
|
|
+ qa_connection_t *c;
|
|
|
+
|
|
|
+ c = qa_connection_new(address);
|
|
|
+ if (!c) return NULL;
|
|
|
+
|
|
|
+ crt = SSL_get_peer_certificate(c->ssl);
|
|
|
+ qa_connection_free(c);
|
|
|
+ return crt;
|
|
|
+}
|