+ * Generate new certificate for specific name
+ *
+ * @param name the subject name to generate a cert for
+ * @return a struct holding the PEM data, NULL on error
+ */
+static struct ProxyGNSCertificate *
+generate_gns_certificate (const char *name)
+{
+ unsigned int serial;
+ size_t key_buf_size;
+ size_t cert_buf_size;
+ gnutls_x509_crt_t request;
+ time_t etime;
+ struct tm *tm_data;
+ struct ProxyGNSCertificate *pgc;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Generating TLS/SSL certificate for `%s'\n",
+ name);
+ GNUNET_break (GNUTLS_E_SUCCESS == gnutls_x509_crt_init (&request));
+ GNUNET_break (GNUTLS_E_SUCCESS == gnutls_x509_crt_set_key (request, proxy_ca.key));
+ pgc = GNUNET_new (struct ProxyGNSCertificate);
+ gnutls_x509_crt_set_dn_by_oid (request, GNUTLS_OID_X520_COUNTRY_NAME,
+ 0, "ZZ", 2);
+ gnutls_x509_crt_set_dn_by_oid (request, GNUTLS_OID_X520_ORGANIZATION_NAME,
+ 0, "GNU Name System", 4);
+ gnutls_x509_crt_set_dn_by_oid (request, GNUTLS_OID_X520_COMMON_NAME,
+ 0, name, strlen (name));
+ GNUNET_break (GNUTLS_E_SUCCESS == gnutls_x509_crt_set_version (request, 3));
+ gnutls_rnd (GNUTLS_RND_NONCE, &serial, sizeof (serial));
+ gnutls_x509_crt_set_serial (request,
+ &serial,
+ sizeof (serial));
+ etime = time (NULL);
+ tm_data = localtime (&etime);
+ gnutls_x509_crt_set_activation_time (request,
+ etime);
+ tm_data->tm_year++;
+ etime = mktime (tm_data);
+ gnutls_x509_crt_set_expiration_time (request,
+ etime);
+ gnutls_x509_crt_sign (request,
+ proxy_ca.cert,
+ proxy_ca.key);
+ key_buf_size = sizeof (pgc->key);
+ cert_buf_size = sizeof (pgc->cert);
+ gnutls_x509_crt_export (request, GNUTLS_X509_FMT_PEM,
+ pgc->cert, &cert_buf_size);
+ gnutls_x509_privkey_export (proxy_ca.key, GNUTLS_X509_FMT_PEM,
+ pgc->key, &key_buf_size);
+ gnutls_x509_crt_deinit (request);
+ return pgc;
+}
+
+
+/**
+ * Function called by MHD with errors, suppresses them all.
+ *
+ * @param cls closure
+ * @param fm format string (`printf()`-style)
+ * @param ap arguments to @a fm
+ */
+static void
+mhd_error_log_callback (void *cls,
+ const char *fm,
+ va_list ap)
+{
+ /* do nothing */
+}
+
+
+/**
+ * Lookup (or create) an SSL MHD instance for a particular domain.
+ *
+ * @param domain the domain the SSL daemon has to serve
+ * @return NULL on error
+ */
+static struct MhdHttpList *
+lookup_ssl_httpd (const char* domain)
+{
+ struct MhdHttpList *hd;
+ struct ProxyGNSCertificate *pgc;
+
+ if (NULL == domain)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ for (hd = mhd_httpd_head; NULL != hd; hd = hd->next)
+ if ( (NULL != hd->domain) &&
+ (0 == strcmp (hd->domain, domain)) )
+ return hd;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Starting fresh MHD HTTPS instance for domain `%s'\n",
+ domain);
+ pgc = generate_gns_certificate (domain);
+ hd = GNUNET_new (struct MhdHttpList);
+ hd->is_ssl = GNUNET_YES;
+ hd->domain = GNUNET_strdup (domain);
+ hd->proxy_cert = pgc;
+ hd->daemon = MHD_start_daemon (MHD_USE_DEBUG | MHD_USE_SSL | MHD_USE_NO_LISTEN_SOCKET,
+ 0,
+ NULL, NULL,
+ &create_response, hd,
+ MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 16,
+ MHD_OPTION_NOTIFY_COMPLETED, &mhd_completed_cb, NULL,
+ MHD_OPTION_NOTIFY_CONNECTION, &mhd_connection_cb, NULL,
+ MHD_OPTION_URI_LOG_CALLBACK, &mhd_log_callback, NULL,
+ MHD_OPTION_EXTERNAL_LOGGER, &mhd_error_log_callback, NULL,
+ MHD_OPTION_HTTPS_MEM_KEY, pgc->key,
+ MHD_OPTION_HTTPS_MEM_CERT, pgc->cert,
+ MHD_OPTION_END);
+ if (NULL == hd->daemon)
+ {
+ GNUNET_free (pgc);
+ GNUNET_free (hd);
+ return NULL;
+ }
+ GNUNET_CONTAINER_DLL_insert (mhd_httpd_head,
+ mhd_httpd_tail,
+ hd);
+ return hd;
+}
+
+
+/**
+ * Task run when a Socks5Request somehow fails to be associated with
+ * an MHD connection (i.e. because the client never speaks HTTP after
+ * the SOCKS5 handshake). Clean up.
+ *
+ * @param cls the `struct Socks5Request *`
+ */
+static void
+timeout_s5r_handshake (void *cls)
+{
+ struct Socks5Request *s5r = cls;
+
+ s5r->timeout_task = NULL;
+ cleanup_s5r (s5r);
+}
+
+
+/**
+ * We're done with the Socks5 protocol, now we need to pass the
+ * connection data through to the final destination, either
+ * direct (if the protocol might not be HTTP), or via MHD
+ * (if the port looks like it should be HTTP).
+ *
+ * @param s5r socks request that has reached the final stage
+ */
+static void
+setup_data_transfer (struct Socks5Request *s5r)
+{
+ struct MhdHttpList *hd;
+ int fd;
+ const struct sockaddr *addr;
+ socklen_t len;
+
+ switch (s5r->port)
+ {
+ case HTTPS_PORT:
+ hd = lookup_ssl_httpd (s5r->domain);
+ if (NULL == hd)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ _("Failed to start HTTPS server for `%s'\n"),
+ s5r->domain);
+ cleanup_s5r (s5r);
+ return;
+ }
+ break;
+ case HTTP_PORT:
+ default:
+ GNUNET_assert (NULL != httpd);
+ hd = httpd;
+ break;
+ }
+ fd = GNUNET_NETWORK_get_fd (s5r->sock);
+ addr = GNUNET_NETWORK_get_addr (s5r->sock);
+ len = GNUNET_NETWORK_get_addrlen (s5r->sock);
+ s5r->state = SOCKS5_SOCKET_WITH_MHD;
+ if (MHD_YES != MHD_add_connection (hd->daemon, fd, addr, len))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ _("Failed to pass client to MHD\n"));
+ cleanup_s5r (s5r);
+ return;
+ }
+ s5r->hd = hd;
+ schedule_httpd (hd);
+ s5r->timeout_task = GNUNET_SCHEDULER_add_delayed (HTTP_HANDSHAKE_TIMEOUT,
+ &timeout_s5r_handshake,
+ s5r);
+}
+
+
+/* ********************* SOCKS handling ************************* */
+
+
+/**
+ * Write data from buffer to socks5 client, then continue with state machine.
+ *
+ * @param cls the closure with the `struct Socks5Request`
+ */
+static void
+do_write (void *cls)
+{
+ struct Socks5Request *s5r = cls;
+ ssize_t len;
+
+ s5r->wtask = NULL;
+ len = GNUNET_NETWORK_socket_send (s5r->sock,
+ s5r->wbuf,
+ s5r->wbuf_len);
+ if (len <= 0)
+ {
+ /* write error: connection closed, shutdown, etc.; just clean up */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Write Error\n");
+ cleanup_s5r (s5r);
+ return;
+ }
+ memmove (s5r->wbuf,
+ &s5r->wbuf[len],
+ s5r->wbuf_len - len);
+ s5r->wbuf_len -= len;
+ if (s5r->wbuf_len > 0)
+ {
+ /* not done writing */
+ s5r->wtask =
+ GNUNET_SCHEDULER_add_write_net (GNUNET_TIME_UNIT_FOREVER_REL,
+ s5r->sock,
+ &do_write, s5r);
+ return;
+ }
+
+ /* we're done writing, continue with state machine! */
+
+ switch (s5r->state)
+ {
+ case SOCKS5_INIT:
+ GNUNET_assert (0);
+ break;
+ case SOCKS5_REQUEST:
+ GNUNET_assert (NULL != s5r->rtask);
+ break;
+ case SOCKS5_DATA_TRANSFER:
+ setup_data_transfer (s5r);
+ return;
+ case SOCKS5_WRITE_THEN_CLEANUP:
+ cleanup_s5r (s5r);
+ return;
+ default:
+ GNUNET_break (0);
+ break;
+ }
+}
+
+
+/**
+ * Return a server response message indicating a failure to the client.
+ *
+ * @param s5r request to return failure code for
+ * @param sc status code to return
+ */
+static void
+signal_socks_failure (struct Socks5Request *s5r,
+ enum Socks5StatusCode sc)
+{
+ struct Socks5ServerResponseMessage *s_resp;
+
+ s_resp = (struct Socks5ServerResponseMessage *) &s5r->wbuf[s5r->wbuf_len];
+ memset (s_resp, 0, sizeof (struct Socks5ServerResponseMessage));
+ s_resp->version = SOCKS_VERSION_5;
+ s_resp->reply = sc;
+ s5r->state = SOCKS5_WRITE_THEN_CLEANUP;
+ if (NULL != s5r->wtask)
+ s5r->wtask =
+ GNUNET_SCHEDULER_add_write_net (GNUNET_TIME_UNIT_FOREVER_REL,
+ s5r->sock,
+ &do_write, s5r);
+}
+
+
+/**
+ * Return a server response message indicating success.
+ *
+ * @param s5r request to return success status message for
+ */
+static void
+signal_socks_success (struct Socks5Request *s5r)
+{
+ struct Socks5ServerResponseMessage *s_resp;
+
+ s_resp = (struct Socks5ServerResponseMessage *) &s5r->wbuf[s5r->wbuf_len];
+ s_resp->version = SOCKS_VERSION_5;
+ s_resp->reply = SOCKS5_STATUS_REQUEST_GRANTED;
+ s_resp->reserved = 0;
+ s_resp->addr_type = SOCKS5_AT_IPV4;
+ /* zero out IPv4 address and port */
+ memset (&s_resp[1],
+ 0,
+ sizeof (struct in_addr) + sizeof (uint16_t));
+ s5r->wbuf_len += sizeof (struct Socks5ServerResponseMessage) +
+ sizeof (struct in_addr) + sizeof (uint16_t);
+ if (NULL == s5r->wtask)
+ s5r->wtask =
+ GNUNET_SCHEDULER_add_write_net (GNUNET_TIME_UNIT_FOREVER_REL,
+ s5r->sock,
+ &do_write, s5r);
+}
+
+
+/**
+ * Process GNS results for target domain.