Przeglądaj źródła

Initial source code commit.

Adding directory src/ containing the sorce code for a small program that, given
a website as input, and eventually a port, connects to the host and downloads
its certificate, printing out its certificate.
Michele Orrù 11 lat temu
rodzic
commit
d2291385fc

+ 120 - 0
src/cmdline.c

@@ -0,0 +1,120 @@
+/**
+ * \file cmdline.c
+ *
+ * \brief Commandline utilities.
+ *
+ * Frontend to QA, proving an easy command line inteface according with the
+ * POSIX standard.
+ */
+#define _GNU_SOURCE
+
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "qa.h"
+
+#define QA_DEFAULT_PORT "443"
+
+
+/**
+ * \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.
+ *
+ * \param[in]  uri     The input uri.
+ * \param[out] host    Place where to store parsed host.
+ * \param[out] service Place where to store parsed service.
+ *
+ * \note \ref uri might be modified during parsing.
+ */
+static void host_port(char* uri, char** host, char** service)
+{
+  char* c;
+
+  if (!(c = strchr(uri, ':')))
+    *host = uri;
+  else {
+    *c = '\0';
+    if (c[1] != '/' && c[2] == '/') {
+      *service = uri;
+      *host = c+3;
+    } else {
+      *service = c+1;
+      *host = uri;
+    }
+  }
+}
+
+
+/**
+ * \brief Prints the usage message, then exit.
+ *
+ * Prints in POSIX format the various options for using qa.
+ *
+ */
+void usage(void)
+{
+  static const char* help_message = "%s usage: %s"
+    " [-p PORT]"
+    " <target>"
+    " \n";
+  fprintf(stderr, help_message,
+          program_invocation_short_name,
+          program_invocation_name);
+}
+
+
+int main(int argc, char** argv)
+{
+  char opt;
+  int option_index;
+  size_t i;
+
+  struct option long_options[] = {
+    {"help", required_argument, NULL, 'h'},
+    {"port", required_argument, NULL, 'p'},
+    {0, 0, 0, 0}
+  };
+  static const char* short_options = "h:p:";
+
+  struct qa_conf conf = {
+    .host = NULL,
+    .port = NULL,
+  };
+
+  while ((opt=getopt_long(argc, argv,
+                          short_options, long_options,
+                          &option_index)) != -1)
+    switch (opt) {
+    case 'h':
+      usage();
+      exit(EXIT_SUCCESS);
+      break;
+    case 'p':
+      conf.port = optarg;
+      break;
+    case '?':
+    default:
+      usage();
+      exit(EXIT_FAILURE);
+    }
+
+  if (optind < argc && !strcmp(argv[optind], "--")) optind++;
+  if (optind != argc-1) {
+    usage();
+    exit(EXIT_FAILURE);
+  }
+
+  host_port(argv[optind], &conf.host, &conf.port);
+  if (!conf.port) conf.port = QA_DEFAULT_PORT;
+
+  return qa_init(&conf);
+}

+ 17 - 0
src/include/qa.h

@@ -0,0 +1,17 @@
+#ifndef _QA_H_
+#define _QA_H_
+
+#include <openssl/bio.h>
+
+struct qa_conf {
+  char *host;
+  char *port;
+};
+
+
+BIO* bio_out;
+
+int qa_init(const struct qa_conf* args);
+
+
+#endif   /* _QA_H_ */

+ 8 - 0
src/include/qa_sock.h

@@ -0,0 +1,8 @@
+#ifndef _QA_SOCK_H_
+#define _QA_SOCK_H_
+
+#include "qa.h"
+
+int init_client(const struct qa_conf *options);
+
+#endif

+ 151 - 0
src/qa.c

@@ -0,0 +1,151 @@
+#include <assert.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <netdb.h>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/x509.h>
+#include <openssl/evp.h>
+#include <openssl/bio.h>
+
+#include "qa.h"
+#include "questions.h"
+#include "qa_sock.h"
+
+/**
+ * \brief Connection informations.
+ */
+struct qa_connection {
+  int socket;   /**< socket file descriptor. */
+  SSL* ssl;     /**< ssl handler for this connection. */
+  SSL_CTX* ctx; /**< ssl context used in this connection. */
+};
+
+
+/**
+ * \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->socket)
+    close(c->socket);
+  if (c->ssl) {
+    SSL_shutdown(c->ssl);
+    SSL_free(c->ssl);
+  }
+  if (c->ctx)
+    SSL_CTX_free(c->ctx);
+
+  free(c);
+}
+
+void qa_abort(const char *reason)
+{
+  //ERR_print_errors_fp(stderr);
+  exit(EXIT_FAILURE);
+}
+
+
+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(const struct qa_conf* conf)
+{
+  struct qa_connection* c;
+  int err;
+
+  c = malloc(sizeof(struct qa_connection));
+  if (!c) qa_abort("No Memory.");
+
+  /* Initialize SSL Library by registering algorithms. */
+  SSL_library_init();
+
+
+  c->ctx = SSL_CTX_new(SSLv23_client_method());
+  if (!c->ctx) {
+    qa_connection_free(c);
+    qa_abort("Cannot create context");
+  }
+
+  /* is also the default. lol. */
+  SSL_CTX_set_verify(c->ctx, SSL_VERIFY_NONE, verify_callback);
+  c->ssl = SSL_new(c->ctx);
+  if (!c->ssl) {
+    qa_connection_free(c);
+    qa_abort("Cannot create ssl handle");
+  }
+
+  if (!(c->socket = init_client(conf))) {
+    qa_connection_free(c);
+    qa_abort("Cannot create socket.");
+  }
+
+  if (!SSL_set_fd(c->ssl, c->socket)) {
+    qa_connection_free(c);
+    qa_abort("Cannot bind socket to ssl session");
+  }
+
+  /* XXX. Handle errors appropriately using error codes from OpenSSL */
+  err = SSL_connect(c->ssl);
+  if (err != 1) {
+    qa_connection_free(c);
+    qa_abort("Cannot Connect");
+  }
+
+  SSL_set_connect_state(c->ssl);
+
+  return c;
+}
+
+
+/**
+ * \brief Given an initial configuration, stuctures the program flow.
+ *
+ * \param[in] args   Initial configuration given from a frontend.
+ */
+int qa_init(const struct qa_conf* args)
+{
+  X509 *crt;
+  struct qa_connection *c;
+  struct qa_question *q;
+
+  /* bind stdout to a BIO shit to be used externally */
+  bio_out = BIO_new_fp(stdout, BIO_NOCLOSE);
+
+  /* create a new connection, and download the certificate */
+  c = qa_connection_new(args);
+  crt = SSL_get_peer_certificate(c->ssl);
+  if (!crt) qa_abort("Cannot obtain certificate");
+
+  register_all_questions();
+for (q=questions.lh_first; q; q = q->qs.le_next) {
+    q->setup();
+    q->test(crt);
+    q->ask(crt);
+    q->teardown();
+  }
+
+  qa_connection_free(c);
+
+  return 0;
+}

+ 50 - 0
src/qa_sock.c

@@ -0,0 +1,50 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include "qa.h"
+
+#define SOCKET_PROTOCOL 0
+#define INVALID_SOCKET  (-1)
+
+int init_client(const struct qa_conf *options)
+{
+  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);
+  }
+
+  for (rp=result; rp; rp = rp->ai_next)  {
+    s = socket(rp->ai_family,
+               rp->ai_socktype,
+               rp->ai_protocol);
+    if (s == INVALID_SOCKET) continue;
+
+    if (rp->ai_protocol == SOCK_STREAM) {
+       i = 0;
+       i = setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char*) &i, sizeof(i));
+       if (i < 0) exit(EXIT_FAILURE);
+    }
+
+    if (connect(s, rp->ai_addr, rp->ai_addrlen) != -1) break;
+  }
+  if (!rp) exit(EXIT_FAILURE);
+
+  return s;
+}

+ 24 - 0
src/questions/allquestions.c

@@ -0,0 +1,24 @@
+#include <sys/queue.h>
+
+#include "questions.h"
+
+
+#define REGISTER_QUESTION(q)                      \
+  {                                               \
+      extern struct qa_question q;                \
+      LIST_INSERT_HEAD(&questions, &q, qs);        \
+  }
+
+/**
+ * /brief Puts registered questions into \ref questions.
+ *
+ * Disposes all registered questions into a global linked list, so that future
+ * procedures can iterate over all possible tests.
+ */
+void register_all_questions(void)
+{
+  LIST_INIT(&questions);
+
+  REGISTER_QUESTION(ExampleQuestion);
+
+}

+ 24 - 0
src/questions/example.c

@@ -0,0 +1,24 @@
+#include "questions.h"
+#include "qa.h"
+
+int example_question_setup(void) { return 0; }
+int example_question_teardown(void) { return 0; }
+/* XXX. apparently openssl does not allow const X509* in get_pkey() func */
+int example_question_test(X509* cert) { return 1; }
+int example_question_ask(X509* cert)
+{
+  EVP_PKEY* pkey;
+
+  pkey = X509_get_pubkey(cert);
+  EVP_PKEY_print_public(bio_out, pkey, 3, NULL);
+}
+
+
+
+struct qa_question ExampleQuestion = {
+  .name = "Example Question",
+  .setup = example_question_setup,
+  .teardown = example_question_teardown,
+  .test = example_question_test,
+  .ask = example_question_ask
+};

+ 25 - 0
src/questions/questions.h

@@ -0,0 +1,25 @@
+#ifndef _QA_QUESTIONS_H_
+#define _QA_QUESTIONS_H_
+
+#include <sys/queue.h>
+
+#include <openssl/x509.h>
+
+
+struct qa_question {
+  const char* name;
+  int (* setup) (void);
+  int (* teardown) ();
+  int (* test) (X509* cert);
+  int (* ask) (X509* cert);
+
+  LIST_ENTRY(qa_question) qs;
+};
+
+
+LIST_HEAD(listhead, qa_question) questions;
+
+void register_all_questions(void);
+
+
+#endif /* _QA_QUESTIONS_H_ */

+ 16 - 0
src/questions/weiner.c

@@ -0,0 +1,16 @@
+int wiener_setup(void)
+{
+  return 0;
+}
+
+int wiener_teardown(void)
+{
+  return 0;
+}
+
+
+
+struct qa_question q_wiener = {
+  .name = "Wiener",
+  -question_setup = wiener_setup,
+};