|
#include <assert.h> |
|
#include <error.h> |
|
#include <gnutls/gnutls.h> |
|
#include <gnutls/x509.h> |
|
#include <stdio.h> |
|
#include <string.h> |
|
#include <time.h> |
|
#include <udns.h> |
|
|
|
#include "evcom.h" // change to <> when evcom will be not inside cert_checker source tree |
|
|
|
#include "crawler.h" |
|
|
|
|
|
static /*@null@*/ struct addrinfo * |
|
build_addrinfo (const char *str_addr, const char *str_port) { |
|
int r = 0; |
|
struct addrinfo *ai; |
|
static const struct addrinfo hints = { |
|
.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV, |
|
.ai_family = AF_UNSPEC, |
|
.ai_socktype = 0, .ai_protocol = 0, |
|
.ai_addrlen = 0, .ai_addr = NULL, |
|
.ai_canonname = NULL, .ai_next = NULL, |
|
}; |
|
|
|
assert(NULL != str_addr); |
|
assert(NULL != str_port); |
|
|
|
r = getaddrinfo(str_addr, str_port, &hints, &ai); |
|
if (0 != r) { |
|
error(0, 0, "crawl_item: error building addrinfo from %s:%s", str_addr, str_port); |
|
return NULL; |
|
} |
|
|
|
return ai; |
|
} |
|
|
|
static int |
|
check_socket_errors (evcom_socket *socket) { |
|
crawl_item *self; |
|
const char *domain; |
|
assert(NULL != socket); |
|
self = (crawl_item *)socket->data; |
|
domain = (const char *)self->domain; |
|
|
|
if (0 != socket->errorno) { |
|
printf("%s\terror\tsocket\n", domain); |
|
error(0, socket->errorno, "socket error"); |
|
evcom_socket_force_close(socket); |
|
return socket->errorno; |
|
} |
|
if (GNUTLS_E_SUCCESS != socket->gnutls_errorno) { |
|
printf("%s\terror\tGNU TLS error\n", domain); |
|
if (GNUTLS_E_FATAL_ALERT_RECEIVED == socket->gnutls_errorno) |
|
error(0, 0, "GNU TLS fatal alert: %s\n", gnutls_alert_get_name(gnutls_alert_get(socket->session))); |
|
else |
|
error(0, 0, "GNU TLS: %s\n", gnutls_strerror(socket->gnutls_errorno)); |
|
evcom_socket_force_close(socket); |
|
return socket->gnutls_errorno; |
|
} |
|
return 0; |
|
} |
|
|
|
static void |
|
on_connect (evcom_socket *socket) { |
|
int r = 0; |
|
crawl_item *self; |
|
assert(NULL != socket); |
|
self = (crawl_item *)socket->data; |
|
|
|
r = check_socket_errors(socket); |
|
if (0 == r) |
|
crawl_item_process_cert(self); |
|
|
|
evcom_socket_force_close(socket); |
|
} |
|
|
|
static void |
|
on_close (evcom_socket *socket) { |
|
crawl_item *self; |
|
assert(NULL != socket); |
|
self = (crawl_item *)socket->data; |
|
|
|
(void)check_socket_errors(socket); |
|
|
|
// important resource freeing is done here |
|
crawl_item_free(self); |
|
} |
|
|
|
static void |
|
on_timeout (evcom_socket *socket) { |
|
const crawl_item *self; |
|
const char *domain; |
|
assert(NULL != socket); |
|
self = (const crawl_item *)socket->data; |
|
domain = (const char *)self->domain; |
|
|
|
printf("%s\ttimeout\t\n", domain); |
|
evcom_socket_force_close(socket); |
|
} |
|
|
|
int |
|
crawl_item_init (/*@notnull@*/ crawl_item *self, |
|
/*@notnull@*/ const char *domain, |
|
float timeout) { |
|
self->domain[0] = '\0'; |
|
strncat(self->domain, domain, sizeof(self->domain) - 1); |
|
|
|
evcom_socket_init(&self->socket, timeout); |
|
self->socket.on_connect = on_connect; |
|
self->socket.on_close = on_close; |
|
self->socket.on_timeout = on_timeout; |
|
|
|
// self backpointer for callbacks |
|
self->socket.data = self; |
|
|
|
return 0; |
|
} |
|
|
|
int |
|
crawl_item_start (/*@notnull@*/ crawl_item *self) { |
|
int r = 0; |
|
const char *domain; |
|
|
|
assert(NULL != self); |
|
domain = (const char *)self->domain; |
|
assert(NULL != domain); |
|
|
|
r = crawler_dns_resolve4_start(self); |
|
if (0 != r) { |
|
printf("%s\terror\tresolve start error\n", self->domain); |
|
return r; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int |
|
crawl_item_connect (/*@notnull@*/ crawl_item *self) { |
|
int r = 0; |
|
size_t i = 0; |
|
struct addrinfo *addrinfo; |
|
|
|
assert(NULL != self); |
|
|
|
for (i = 0; i < self->addr_count; i++) { |
|
// some particular address may not fit into our static buffer |
|
if ('\0' == self->str_addr[i][0]) |
|
continue; |
|
addrinfo = build_addrinfo(self->str_addr[i], "443"); |
|
if (NULL == addrinfo) |
|
continue; |
|
|
|
tls_session_init(self); |
|
r = evcom_socket_connect(&self->socket, addrinfo); |
|
freeaddrinfo(addrinfo); |
|
if (0 == r) { |
|
evcom_socket_attach(EV_DEFAULT_ &self->socket); |
|
return 0; |
|
} |
|
gnutls_deinit(self->socket.session); |
|
self->socket.session = NULL; |
|
} |
|
|
|
// no successful connect happened. propagate fail |
|
return r; |
|
} |
|
|
|
/* |
|
* Reads certificate info, if any. Outputs crawling results. |
|
*/ |
|
void |
|
crawl_item_process_cert (/*@notnull@*/ crawl_item *self) { |
|
// error-checking closure :) PC stands for process certificate |
|
#define CRAWLER_PC_CHECK_GNUTLS_RESULT_DEINIT_CERT do { \ |
|
if (GNUTLS_E_SUCCESS != r) { \ |
|
printf("%s\terror\tin GNU TLS while processing cert", domain); \ |
|
fprintf(stderr, "crawl_item: process_cert:%d domain: %s GNU TLS error: ", \ |
|
__LINE__, domain); \ |
|
gnutls_perror(r); \ |
|
gnutls_x509_crt_deinit (cert); \ |
|
return; \ |
|
} \ |
|
} while(0) |
|
|
|
int r; |
|
const gnutls_datum_t *cert_list = NULL; |
|
unsigned int cert_list_size = 0; |
|
gnutls_x509_crt_t cert; |
|
time_t expiration_time, activation_time; |
|
const struct tm *expiration_time_tm = NULL, *activation_time_tm = NULL; |
|
char expiration_time_str[64], activation_time_str[64]; |
|
gnutls_pk_algorithm_t algo = 0; |
|
unsigned int bits = 0; |
|
const char *algo_name = NULL; |
|
size_t size = 0; |
|
char dn[1024], issuer_dn[1024]; |
|
evcom_socket *client; |
|
const char *domain; |
|
|
|
assert(NULL != self); |
|
client = &self->socket; |
|
assert(NULL != client); |
|
assert(NULL != client->session); |
|
domain = (const char *)self->domain; |
|
DPRINT1("crawl_item: processing certificate for domain: %s\n", domain); |
|
|
|
if (GNUTLS_CRT_X509 != gnutls_certificate_type_get(client->session)) { |
|
printf("%s\terror\tprocessing certificate\n", domain); |
|
error(0, 0, "crawl_item: process_cert: domain: %s certificate type is not x.509\n", domain); |
|
return; |
|
} |
|
|
|
cert_list = gnutls_certificate_get_peers(client->session, &cert_list_size); |
|
if (NULL == cert_list) { |
|
printf("%s\terror\tprocessing certificate\n", domain); |
|
error(0, 0, "crawl_item: process_cert: domain: %s error getting certificates\n", domain); |
|
return; |
|
} |
|
|
|
r = gnutls_x509_crt_init(&cert); |
|
CRAWLER_PC_CHECK_GNUTLS_RESULT_DEINIT_CERT; |
|
r = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER); |
|
CRAWLER_PC_CHECK_GNUTLS_RESULT_DEINIT_CERT; |
|
|
|
activation_time = gnutls_x509_crt_get_activation_time(cert); |
|
activation_time_tm = gmtime(&activation_time); |
|
size = strftime(activation_time_str, sizeof(activation_time_str) - 1, "%FT%T", activation_time_tm); |
|
if (0 == size) { |
|
printf("%s\terror\tprocessing certificate\n", domain); |
|
error(0, 0, "crawl_item: process_cert: domain: %s error in strftime for activation time: %ld\n", domain, activation_time); |
|
gnutls_x509_crt_deinit(cert); |
|
return; |
|
} |
|
|
|
expiration_time = gnutls_x509_crt_get_expiration_time(cert); |
|
expiration_time_tm = gmtime(&expiration_time); |
|
size = strftime(expiration_time_str, sizeof(expiration_time_str) - 1, "%FT%T", expiration_time_tm); |
|
if (0 == size) { |
|
printf("%s\terror\tin strftime for cert expiration time: %ld\n", domain, expiration_time); |
|
error(0, 0, "crawl_item: process_cert: domain: %s error in strftime for expiration time: %ld\n", domain, activation_time); |
|
gnutls_x509_crt_deinit(cert); |
|
return; |
|
} |
|
|
|
algo = gnutls_x509_crt_get_pk_algorithm (cert, &bits); |
|
|
|
size = sizeof(dn); |
|
memset(dn, 0, size); |
|
r = gnutls_x509_crt_get_dn(cert, dn, &size); |
|
CRAWLER_PC_CHECK_GNUTLS_RESULT_DEINIT_CERT; |
|
|
|
size = sizeof(issuer_dn); |
|
memset(issuer_dn, 0, size); |
|
r = gnutls_x509_crt_get_issuer_dn(cert, issuer_dn, &size); |
|
CRAWLER_PC_CHECK_GNUTLS_RESULT_DEINIT_CERT; |
|
|
|
algo_name = gnutls_pk_algorithm_get_name(algo); |
|
printf("%s\tok\tDN\t%s\tIDN\t%s\tSINCE\t%s\tEXPIRES\t%s\tPK-ALGO\t%s\n", |
|
domain, dn, issuer_dn, activation_time_str, expiration_time_str, algo_name); |
|
|
|
// important cleanup |
|
gnutls_x509_crt_deinit(cert); |
|
} |
|
|
|
void |
|
crawl_item_free (/*@notnull@*/ crawl_item *self) { |
|
evcom_socket_detach(&self->socket); |
|
|
|
if (NULL != self->socket.session) { |
|
gnutls_deinit(self->socket.session); |
|
self->socket.session = NULL; |
|
} |
|
} |