unbound-anchor work

git-svn-id: file:///svn/unbound/trunk@2245 be551aaa-1e26-0410-a405-d3ace91eadb9
This commit is contained in:
Wouter Wijngaards
2010-09-24 14:02:27 +00:00
parent 9f44640fa7
commit eb3114596c
+617 -10
View File
@@ -50,6 +50,15 @@
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
#ifdef HAVE_OPENSSL_SSL_H
#include <openssl/ssl.h>
#endif
#ifdef HAVE_OPENSSL_ERR_H
#include <openssl/err.h>
#endif
#ifdef HAVE_OPENSSL_RAND_H
#include <openssl/rand.h>
#endif
#include <openssl/x509.h>
#include <openssl/pem.h>
@@ -66,6 +75,8 @@
#define P7SNAME "/root-anchors/root-anchors.p7s"
/** path on HTTPS server to pem file */
#define PEMNAME "/root-anchors/icannbundle.pem"
/** port number for https access */
#define HTTPS_PORT 443
/** verbosity for this application */
static int verb = 0;
@@ -78,6 +89,8 @@ struct ip_list {
socklen_t len;
/** address ready to connect to */
struct sockaddr_storage addr;
/** has the address been used */
int used;
};
/** Give unbound-anchor usage, and exit (1). */
@@ -167,6 +180,33 @@ create_unbound_context(char* res_conf, char* root_hints, char* debugconf,
return ctx;
}
/** printout certificate in detail */
static void
verb_cert(char* msg, X509* x)
{
if(verb == 0) return;
if(verb == 1) {
if(msg) printf("%s\n", msg);
X509_print_ex_fp(stdout, x, 0, -1^(X509_FLAG_NO_SUBJECT
|X509_FLAG_NO_ISSUER|X509_FLAG_NO_VALIDITY));
return;
}
if(msg) printf("%s\n", msg);
X509_print_fp(stdout, x);
}
/** printout certificates in detail */
static void
verb_certs(char* msg, STACK_OF(X509)* sk)
{
int i, num = sk_X509_num(sk);
if(verb == 0) return;
for(i=0; i<num; i++) {
printf("%s (%d/%d)\n", msg, i, num);
verb_cert(NULL, sk_X509_value(sk, i));
}
}
/** read certificates from a PEM bio */
static STACK_OF(X509)*
read_cert_bio(BIO* bio)
@@ -233,6 +273,7 @@ read_cert_file(char* file)
static STACK_OF(X509)*
read_builtin_cert(void)
{
/* The ICANN CA fetched at 24 Sep 2010. Valid to 2028 */
const char* builtin_cert =
"-----BEGIN CERTIFICATE-----\n"
"MIIDdzCCAl+gAwIBAgIBATANBgkqhkiG9w0BAQsFADBdMQ4wDAYDVQQKEwVJQ0FO\n"
@@ -257,7 +298,8 @@ read_builtin_cert(void)
"-----END CERTIFICATE-----\n"
;
STACK_OF(X509)* sk;
BIO *bio = BIO_new_mem_buf((void*)builtin_cert, strlen(builtin_cert));
BIO *bio = BIO_new_mem_buf((void*)builtin_cert,
(int)strlen(builtin_cert));
if(!bio) {
if(verb) printf("out of memory\n");
exit(0);
@@ -282,13 +324,129 @@ read_cert_or_builtin(char* file, int* write_cert)
sk = read_builtin_cert();
}
if(verb) printf("have %d trusted certificates\n", sk_X509_num(sk));
verb_certs("trusted certificates", sk);
return sk;
}
/** printout IP address with message */
static void
verb_addr(char* msg, struct ip_list* ip)
{
if(verb) {
char out[100];
void* a = &((struct sockaddr_in*)&ip->addr)->sin_addr;
if(ip->len != (socklen_t)sizeof(struct sockaddr_in))
a = &((struct sockaddr_in6*)&ip->addr)->sin6_addr;
if(inet_ntop((int)((struct sockaddr_in*)&ip->addr)->sin_family,
a, out, (socklen_t)sizeof(out))==0)
printf("%s (inet_ntop error)\n", msg);
else printf("%s %s\n", msg, out);
}
}
/** create ip_list entry for a RR record */
static struct ip_list*
RR_to_ip(int tp, char* data, int len)
{
struct ip_list* ip = (struct ip_list*)calloc(1, sizeof(*ip));
uint16_t p = HTTPS_PORT;
if(tp == LDNS_RR_TYPE_A) {
struct sockaddr_in* sa = (struct sockaddr_in*)&ip->addr;
ip->len = (socklen_t)sizeof(*sa);
sa->sin_family = AF_INET;
sa->sin_port = (in_port_t)htons(p);
if(len != (int)sizeof(sa->sin_addr)) {
if(verb) printf("skipped badly formatted A\n");
free(ip);
return NULL;
}
memmove(&sa->sin_addr, data, sizeof(sa->sin_addr));
} else if(tp == LDNS_RR_TYPE_AAAA) {
struct sockaddr_in6* sa = (struct sockaddr_in6*)&ip->addr;
ip->len = (socklen_t)sizeof(*sa);
sa->sin6_family = AF_INET6;
sa->sin6_port = (in_port_t)htons(p);
if(len != (int)sizeof(sa->sin6_addr)) {
if(verb) printf("skipped badly formatted AAAA\n");
free(ip);
return NULL;
}
memmove(&sa->sin6_addr, data, sizeof(sa->sin6_addr));
} else {
if(verb) printf("internal error: bad type in RRtoip\n");
free(ip);
return NULL;
}
verb_addr("resolved server address", ip);
return ip;
}
/** Resolve name, type, class and add addresses to iplist */
static void
add_ip(struct ub_ctx* ctx, char* host, int tp, int cl, struct ip_list** head)
resolve_host_ip(struct ub_ctx* ctx, char* host, int tp, int cl, struct ip_list** head)
{
struct ub_result* res = NULL;
int r;
int i;
r = ub_resolve(ctx, host, tp, cl, &res);
if(r) {
if(verb) printf("error: resolve %s %s: %s\n", host,
(tp==LDNS_RR_TYPE_A)?"A":"AAAA", ub_strerror(r));
return;
}
if(!res) {
if(verb) printf("out of memory\n");
exit(0);
}
for(i = 0; res->data[i]; i++) {
struct ip_list* ip = RR_to_ip(tp, res->data[i], res->len[i]);
if(!ip) continue;
ip->next = *head;
*head = ip;
}
ub_resolve_free(res);
}
/** parse a text IP address into a sockaddr */
static struct ip_list*
parse_ip_addr(char* str)
{
socklen_t len = 0;
struct sockaddr_storage* addr = NULL;
struct sockaddr_in6 a6;
struct sockaddr_in a;
struct ip_list* ip;
uint16_t p = HTTPS_PORT;
memset(&a6, 0, sizeof(a6));
memset(&a, 0, sizeof(a));
if(inet_pton(AF_INET6, str, &a6.sin6_addr) > 0) {
/* it is an IPv6 */
a6.sin6_family = AF_INET6;
a6.sin6_port = (in_port_t)htons(p);
addr = (struct sockaddr_storage*)&a6;
len = (socklen_t)sizeof(struct sockaddr_in6);
}
if(inet_pton(AF_INET, str, &a.sin_addr) > 0) {
/* it is an IPv4 */
a.sin_family = AF_INET;
a.sin_port = (in_port_t)htons(p);
addr = (struct sockaddr_storage*)&a;
len = (socklen_t)sizeof(struct sockaddr_in);
}
if(!len) return NULL;
ip = (struct ip_list*)calloc(1, sizeof(*ip));
if(!ip) {
if(verb) printf("out of memory\n");
exit(0);
}
ip->len = len;
memmove(&ip->addr, addr, len);
if(verb) printf("server address is %s\n", str);
return ip;
}
/**
@@ -302,7 +460,9 @@ resolve_name(char* host, char* res_conf, char* root_hints, char* debugconf,
struct ub_ctx* ctx;
struct ip_list* list = NULL;
/* first see if name is an IP address itself */
/* TODO */
if( (list=parse_ip_addr(host)) ) {
return list;
}
/* create resolver context */
ctx = create_unbound_context(res_conf, root_hints, debugconf,
@@ -310,18 +470,439 @@ resolve_name(char* host, char* res_conf, char* root_hints, char* debugconf,
/* try resolution of A */
if(!ip6only) {
add_ip(ctx, host, LDNS_RR_TYPE_A, LDNS_RR_CLASS_IN, &list);
resolve_host_ip(ctx, host, LDNS_RR_TYPE_A,
LDNS_RR_CLASS_IN, &list);
}
/* try resolution of AAAA */
if(!ip4only) {
add_ip(ctx, host, LDNS_RR_TYPE_AAAA, LDNS_RR_CLASS_IN, &list);
resolve_host_ip(ctx, host, LDNS_RR_TYPE_AAAA,
LDNS_RR_CLASS_IN, &list);
}
ub_ctx_delete(ctx);
if(!list) {
if(verb) printf("%s has no IP addresses I can use\n", host);
exit(0);
}
return list;
}
/** clear used flags */
static void
wipe_ip_usage(struct ip_list* p)
{
while(p) {
p->used = 0;
p = p->next;
}
}
/** cound unused IPs */
static int
count_unused(struct ip_list* p)
{
int num = 0;
while(p) {
if(!p->used) num++;
p = p->next;
}
return num;
}
/** pick random unused element from IP list */
static struct ip_list*
pick_random_ip(struct ip_list* list)
{
struct ip_list* p = list;
int num = count_unused(list);
int sel;
if(num == 0) return NULL;
/* not perfect, but random enough */
sel = (int)ldns_get_random() % num;
while(sel > 0 && p) {
if(!p->used) sel--;
p = p->next;
}
while(p && p->used)
p = p->next;
if(!p) return NULL; /* robustness */
return p;
}
/** close the fd */
static void
fd_close(int fd)
{
#ifndef USE_WINSOCK
close(fd);
#else
closesocket(fd);
#endif
}
/** connect to IP address */
static int
connect_to_ip(struct ip_list* ip)
{
int fd;
verb_addr("connect to", ip);
fd = socket(ip->len==(socklen_t)sizeof(struct sockaddr_in)?
AF_INET:AF_INET6, SOCK_STREAM, 0);
if(fd == -1) {
if(verb) printf("socket: %s\n", strerror(errno));
return -1;
}
if(connect(fd, (struct sockaddr*)&ip->addr, ip->len) < 0) {
if(verb) printf("connect: %s\n", strerror(errno));
fd_close(fd);
return -1;
}
return fd;
}
/** initiate TLS on a connection */
static SSL*
TLS_initiate(int fd)
{
SSL_CTX* sslctx;
SSL* ssl;
X509* x;
int r;
sslctx = SSL_CTX_new(SSLv23_client_method());
if(!sslctx) {
if(verb) printf("SSL_CTX_new error\n");
return NULL;
}
ssl = SSL_new(sslctx);
if(!ssl) {
if(verb) printf("SSL_new error\n");
return NULL;
}
SSL_set_connect_state(ssl);
(void)SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
if(!SSL_set_fd(ssl, fd)) {
if(verb) printf("SSL_set_fd error\n");
SSL_free(ssl);
return NULL;
}
while(1) {
ERR_clear_error();
if( (r=SSL_do_handshake(ssl)) == 1)
break;
r = SSL_get_error(ssl, r);
if(r != SSL_ERROR_WANT_READ && r != SSL_ERROR_WANT_WRITE) {
if(verb) printf("SSL handshake failed\n");
SSL_free(ssl);
return NULL;
}
/* wants to be called again */
}
x = SSL_get_peer_certificate(ssl);
if(!x) {
if(verb) printf("Server presented no peer certificate\n");
SSL_free(ssl);
return NULL;
}
verb_cert("server SSL certificate", x);
X509_free(x);
return ssl;
}
/** perform neat TLS shutdown */
static void
TLS_shutdown(int fd, SSL* ssl)
{
/* shutdown the SSL connection nicely */
if(SSL_shutdown(ssl) == 0) {
SSL_shutdown(ssl);
}
SSL_free(ssl);
fd_close(fd);
}
/** write a line over SSL */
static int
write_ssl_line(SSL* ssl, char* str, char* sec)
{
char buf[1024];
size_t l;
if(sec) {
snprintf(buf, sizeof(buf), str, sec);
} else {
snprintf(buf, sizeof(buf), "%s", str);
}
l = strlen(buf);
if(l+2 >= sizeof(buf)) {
if(verb) printf("line too long\n");
return 0;
}
if(verb >= 2) printf("SSL_write: %s\n", buf);
buf[l] = '\r';
buf[l+1] = '\n';
buf[l+2] = 0;
/* add \r\n */
if(SSL_write(ssl, buf, (int)strlen(buf)) <= 0) {
if(verb) printf("could not SSL_write %s", str);
return 0;
}
return 1;
}
static int
process_one_header(char* buf, size_t* clen, int* chunked)
{
if(verb>=2) printf("header: '%s'\n", buf);
if(strncasecmp(buf, "HTTP/1.1 ", 9) == 0) {
/* check returncode */
if(buf[9] != '2') {
if(verb) printf("bad status %s\n", buf+9);
return 0;
}
} else if(strncasecmp(buf, "Content-Length: ", 16) == 0) {
if(!*chunked)
*clen = (size_t)atoi(buf+16);
} else if(strncasecmp(buf, "Transfer-Encoding: chunked", 19+7) == 0) {
*clen = 0;
*chunked = 1;
}
return 1;
}
/** read one file from SSL
* zero terminates.
* skips \r\n (but not copied to buf).
*/
static int
read_ssl_line(SSL* ssl, char* buf, size_t len)
{
size_t n = 0;
int r;
int endnl = 0;
while(1) {
if(n >= len) {
if(verb) printf("line too long\n");
return 0;
}
if((r = SSL_read(ssl, buf+n, 1)) <= 0) {
if(SSL_get_error(ssl, r) == SSL_ERROR_ZERO_RETURN) {
/* EOF */
break;
}
if(verb) printf("could not SSL_read\n");
return 0;
}
if(endnl && buf[n] == '\n') {
break;
} else if(endnl) {
/* bad data */
if(verb) printf("error: stray linefeeds\n");
return 0;
} else if(buf[n] == '\r') {
/* skip \r, and also \n on the wire */
endnl = 1;
continue;
} else if(buf[n] == '\n') {
/* skip the \n, we are done */
break;
} else n++;
}
buf[n] = 0;
return 1;
}
/** read http headers and process them */
static size_t
read_http_headers(SSL* ssl, size_t* clen)
{
char buf[1024];
int chunked = 0;
*clen = 0;
while(read_ssl_line(ssl, buf, sizeof(buf))) {
if(buf[0] == 0)
return 1;
if(!process_one_header(buf, clen, &chunked))
return 0;
}
return 0;
}
/** read a data chunk */
static char*
read_data_chunk(SSL* ssl, size_t len)
{
int r;
char* data = malloc(len+1);
if(!data) {
if(verb) printf("out of memory\n");
return NULL;
}
if((r = SSL_read(ssl, data, len)) <= 0) {
if(SSL_get_error(ssl, r) == SSL_ERROR_ZERO_RETURN) {
/* EOF */
if(verb) printf("could not SSL_read: unexpected EOF\n");
free(data);
return NULL;
}
if(verb) printf("could not SSL_read\n");
free(data);
return NULL;
}
if(verb>=2) printf("read %d data\n", (int)len);
data[len] = 0;
return data;
}
/** parse chunk header */
static int
parse_chunk_header(char* buf, size_t* result)
{
char* e = NULL;
size_t v = (size_t)strtol(buf, &e, 16);
if(e == buf)
return 0;
*result = v;
return 1;
}
/** read chunked data from connection */
static BIO*
do_chunked_read(SSL* ssl)
{
char buf[1024];
size_t len;
char* body;
BIO* mem = BIO_new(BIO_s_mem());
if(!mem) {
if(verb) printf("out of memory\n");
return NULL;
}
while(read_ssl_line(ssl, buf, sizeof(buf))) {
/* read the chunked start line */
if(verb>=2) printf("chunk header: %s\n", buf);
if(!parse_chunk_header(buf, &len)) {
BIO_free(mem);
return NULL;
}
if(verb>=2) printf("chunk len: %d\n", (int)len);
if(len == 0) {
char z = 0;
/* skip end-of-chunk-trailer lines,
* until the empty line after that */
do {
if(!read_ssl_line(ssl, buf, sizeof(buf))) {
BIO_free(mem);
return NULL;
}
} while (strlen(buf) > 0);
/* end of chunks, zero terminate it */
if(BIO_write(mem, &z, 1) <= 0) {
if(verb) printf("out of memory\n");
BIO_free(mem);
return NULL;
}
return mem;
}
/* read the chunked body */
body = read_data_chunk(ssl, len);
if(!body) {
BIO_free(mem);
return NULL;
}
if(BIO_write(mem, body, len) <= 0) {
if(verb) printf("out of memory\n");
BIO_free(mem);
return NULL;
}
/* skip empty line after data chunk */
if(!read_ssl_line(ssl, buf, sizeof(buf))) {
BIO_free(mem);
return NULL;
}
}
BIO_free(mem);
return NULL;
}
/** start HTTP1.1 transaction on SSL */
static int
write_http_get(SSL* ssl, char* pathname, char* urlname)
{
if(write_ssl_line(ssl, "GET %s HTTP/1.1", pathname) &&
write_ssl_line(ssl, "Host: %s", urlname) &&
write_ssl_line(ssl, "User-Agent: unbound-anchor/%s",
PACKAGE_VERSION) &&
/*write_ssl_line(ssl, "Connection: close", NULL) &&*/
write_ssl_line(ssl, "", NULL)) {
return 1;
}
return 0;
}
/** read HTTP result from SSL */
static BIO*
read_http_result(SSL* ssl)
{
size_t len = 0;
char* data;
BIO* m;
if(!read_http_headers(ssl, &len)) {
return NULL;
}
if(len == 0) {
/* do the chunked version */
BIO* tmp = do_chunked_read(ssl);
char* d = NULL;
long l;
l = BIO_get_mem_data(tmp, &d);
if(verb) printf("chunked data is %d\n", (int)l);
if(l == 0 || d == NULL) {
if(verb) printf("out of memory\n");
return NULL;
}
data = strdup(d);
if(data == NULL) {
if(verb) printf("out of memory\n");
return NULL;
}
BIO_free(tmp);
} else {
data = read_data_chunk(ssl, len);
}
m = BIO_new_mem_buf(data, -1);
if(!m) {
if(verb) printf("out of memory\n");
exit(0);
}
return m;
}
/** https to an IP addr, return BIO with pathname or NULL */
static BIO*
https_to_ip(struct ip_list* ip, char* pathname, char* urlname)
{
int fd;
SSL* ssl;
BIO* bio;
fd = connect_to_ip(ip);
if(fd == -1)
return NULL;
ssl = TLS_initiate(fd);
if(!ssl) {
fd_close(fd);
return NULL;
}
if(!write_http_get(ssl, pathname, urlname)) {
if(verb) printf("could not write to server\n");
SSL_free(ssl);
fd_close(fd);
return NULL;
}
bio = read_http_result(ssl);
TLS_shutdown(fd, ssl);
return bio;
}
/**
* Do a HTTPS, HTTP1.1 over TLS, to fetch a file
* @param ip_list: list of IP addresses to use to fetch from.
@@ -329,8 +910,24 @@ resolve_name(char* host, char* res_conf, char* root_hints, char* debugconf,
* @return a memory BIO with the file in it.
*/
static BIO*
https(struct ip_list* ip_list, char* pathname)
https(struct ip_list* ip_list, char* pathname, char* urlname)
{
struct ip_list* ip;
BIO* bio = NULL;
/* try random address first, and work through the list */
wipe_ip_usage(ip_list);
while( (ip = pick_random_ip(ip_list)) ) {
ip->used = 1;
bio = https_to_ip(ip, pathname, urlname);
if(bio) break;
}
if(!bio) {
if(verb) printf("could not fetch %s\n", pathname);
exit(0);
} else {
if(verb) printf("fetched %s\n", pathname);
}
return bio;
}
/** perform actual certupdate work */
@@ -343,7 +940,7 @@ do_certupdate(char* root_anchor_file, char* root_cert_file,
int write_cert = 0;
STACK_OF(X509)* cert;
BIO *pem, *xml, *p7s;
struct ip_list* ip_list;
struct ip_list* ip_list = NULL;
/* read pem file or provide builtin */
cert = read_cert_or_builtin(root_cert_file, &write_cert);
@@ -352,11 +949,15 @@ do_certupdate(char* root_anchor_file, char* root_cert_file,
ip4only, ip6only);
/* fetch the necessary files over HTTPS */
xml = https(ip_list, xmlname);
p7s = https(ip_list, p7sname);
pem = https(ip_list, pemname);
xml = https(ip_list, xmlname, urlname);
p7s = https(ip_list, p7sname, urlname);
pem = https(ip_list, pemname, urlname);
/* update the pem file (optional) */
/*do_pem_update(cert, pem, &write_cert);*/
if(write_cert) {
/*write_cert_file(root_cert_file, cert);*/
}
/* verify and update the root anchor */
/* verify xml file */
@@ -364,6 +965,8 @@ do_certupdate(char* root_anchor_file, char* root_cert_file,
/* reinstate 5011 tracking */
if(verb) printf("success: the anchor has been updated "
"using the cert\n");
/* TODO free the data buffers inside them */
BIO_free(xml);
BIO_free(p7s);
BIO_free(pem);
@@ -706,6 +1309,10 @@ int main(int argc, char* argv[])
argv += optind;
if(argc != 0)
usage();
ERR_load_crypto_strings();
ERR_load_SSL_strings();
OpenSSL_add_all_algorithms();
(void)SSL_library_init();
return do_root_update_work(root_anchor_file, root_cert_file,
urlname, xmlname, p7sname, pemname,
res_conf, root_hints, debugconf, ip4only, ip6only, force);