/*
* svr.c - dnssec-trigger server implementation
*
* Copyright (c) 2011, NLnet Labs. All rights reserved.
*
* This software is open source.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the NLNET LABS nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* \file
*
* This file contains the server implementation.
*/
#include "config.h"
#include "svr.h"
#include "cfg.h"
#include "log.h"
#include "http.h"
#include "probe.h"
#include "netevent.h"
#include "net_help.h"
#include "reshook.h"
#include "update.h"
#ifdef USE_WINSOCK
#include "winsock_event.h"
#endif
struct svr* global_svr = NULL;
static int setup_ssl_ctx(struct svr* svr);
static int setup_listen(struct svr* svr);
static void sslconn_delete(struct sslconn* sc);
static int sslconn_readline(struct sslconn* sc);
static int sslconn_write(struct sslconn* sc);
static int sslconn_checkclose(struct sslconn* sc);
static void sslconn_shutdown(struct sslconn* sc);
static void sslconn_command(struct sslconn* sc);
static void sslconn_persist_command(struct sslconn* sc);
static void send_results_to_con(struct svr* svr, struct sslconn* s);
struct svr* svr_create(struct cfg* cfg)
{
struct svr* svr = (struct svr*)calloc(1, sizeof(*svr));
if(!svr) return NULL;
global_svr = svr;
svr->max_active = 32;
svr->cfg = cfg;
svr->base = comm_base_create(0);
ldns_init_random(NULL, 0);
if(!svr->base) {
log_err("cannot create base");
svr_delete(svr);
return NULL;
}
svr->udp_buffer = ldns_buffer_new(65553);
if(!svr->udp_buffer) {
log_err("out of memory");
svr_delete(svr);
return NULL;
}
svr->retry_timer = comm_timer_create(svr->base, &svr_retry_callback,
svr);
svr->tcp_timer = comm_timer_create(svr->base, &svr_tcp_callback, svr);
if(!svr->retry_timer || !svr->tcp_timer) {
log_err("out of memory");
svr_delete(svr);
return NULL;
}
if(cfg->check_updates) {
svr->update = selfupdate_create(svr, cfg);
if(!svr->update) {
log_err("out of memory");
svr_delete(svr);
return NULL;
}
}
/* setup SSL_CTX */
if(!setup_ssl_ctx(svr)) {
log_err("cannot setup SSL context");
svr_delete(svr);
return NULL;
}
/* create listening */
if(!setup_listen(svr)) {
log_err("cannot setup listening socket");
svr_delete(svr);
return NULL;
}
return svr;
}
void svr_delete(struct svr* svr)
{
struct listen_list* ll, *nll;
if(!svr) return;
/* delete busy */
while(svr->busy_list) {
(void)SSL_shutdown(svr->busy_list->ssl);
sslconn_delete(svr->busy_list);
}
/* delete listening */
ll = svr->listen;
while(ll) {
nll = ll->next;
comm_point_delete(ll->c);
free(ll);
ll=nll;
}
/* delete probes */
probe_list_delete(svr->probes);
if(svr->ctx) {
SSL_CTX_free(svr->ctx);
}
selfupdate_delete(svr->update);
ldns_buffer_free(svr->udp_buffer);
comm_timer_delete(svr->retry_timer);
comm_timer_delete(svr->tcp_timer);
http_general_delete(svr->http);
comm_base_delete(svr->base);
free(svr);
}
static int setup_ssl_ctx(struct svr* s)
{
char* s_cert;
char* s_key;
s->ctx = SSL_CTX_new(SSLv23_server_method());
if(!s->ctx) {
log_crypto_err("could not SSL_CTX_new");
return 0;
}
/* no SSLv2 because has defects */
if((SSL_CTX_set_options(s->ctx, SSL_OP_NO_SSLv2) & SSL_OP_NO_SSLv2)
!= SSL_OP_NO_SSLv2){
log_crypto_err("could not set SSL_OP_NO_SSLv2");
return 0;
}
s_cert = s->cfg->server_cert_file;
s_key = s->cfg->server_key_file;
verbose(VERB_ALGO, "setup SSL certificates");
if(!SSL_CTX_use_certificate_file(s->ctx,s_cert,SSL_FILETYPE_PEM)) {
log_err("Error for server-cert-file: %s", s_cert);
log_crypto_err("Error in SSL_CTX use_certificate_file");
return 0;
}
if(!SSL_CTX_use_PrivateKey_file(s->ctx,s_key,SSL_FILETYPE_PEM)) {
log_err("Error for server-key-file: %s", s_key);
log_crypto_err("Error in SSL_CTX use_PrivateKey_file");
return 0;
}
if(!SSL_CTX_check_private_key(s->ctx)) {
log_err("Error for server-key-file: %s", s_key);
log_crypto_err("Error in SSL_CTX check_private_key");
return 0;
}
if(!SSL_CTX_load_verify_locations(s->ctx, s_cert, NULL)) {
log_crypto_err("Error setting up SSL_CTX verify locations");
return 0;
}
SSL_CTX_set_client_CA_list(s->ctx, SSL_load_client_CA_file(s_cert));
SSL_CTX_set_verify(s->ctx, SSL_VERIFY_PEER, NULL);
return 1;
}
static int setup_listen(struct svr* svr)
{
const char* str="127.0.0.1";
struct sockaddr_storage addr;
socklen_t len;
int s;
int fam;
struct listen_list* e;
#if defined(SO_REUSEADDR) || defined(IPV6_V6ONLY)
int on = 1;
#endif
if(!ipstrtoaddr(str, svr->cfg->control_port, &addr, &len)) {
log_err("cannot parse ifname %s", str);
return 0;
}
if(strchr(str, ':')) fam = AF_INET6;
else fam = AF_INET;
s = socket(fam, SOCK_STREAM, 0);
if(s == -1) {
log_err("socket %s: %s", str, strerror(errno));
return 0;
}
#ifdef SO_REUSEADDR
if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&on,
(socklen_t)sizeof(on)) < 0) {
log_err("setsockopt(.. SO_REUSEADDR ..) failed: %s",
strerror(errno));
}
#endif
#if defined(IPV6_V6ONLY)
if(fam == AF_INET6) {
if(setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
(void*)&on, (socklen_t)sizeof(on)) < 0) {
log_err("setsockopt(..., IPV6_V6ONLY, ...) failed: %s",
strerror(errno));
}
}
#endif
if(bind(s, (struct sockaddr*)&addr, len) != 0) {
fatal_exit("can't bind tcp socket %s: %s", str, strerror(errno));
}
fd_set_nonblock(s);
if(listen(s, 15) == -1) {
log_err("can't listen: %s", strerror(errno));
}
/* add entry */
e = (struct listen_list*)calloc(1, sizeof(*e));
if(!e) {
fatal_exit("out of memory");
}
e->c = comm_point_create_raw(svr->base, s, 0, handle_ssl_accept, NULL);
e->c->do_not_close = 0;
e->next = svr->listen;
svr->listen = e;
return 1;
}
void svr_service(struct svr* svr)
{
comm_base_dispatch(svr->base);
}
static void sslconn_delete(struct sslconn* sc)
{
struct sslconn** pp;
if(!sc) return;
/* delete and remove from busylist */
for(pp = &global_svr->busy_list; *pp; pp = &((*pp)->next)) {
if((*pp) == sc) {
(*pp) = sc->next;
break;
}
}
global_svr->active--;
if(sc->buffer)
ldns_buffer_free(sc->buffer);
comm_point_delete(sc->c);
if(sc->ssl)
SSL_free(sc->ssl);
free(sc);
}
int handle_ssl_accept(struct comm_point* c, void* ATTR_UNUSED(arg), int err,
struct comm_reply* ATTR_UNUSED(reply_info))
{
struct sockaddr_storage addr;
socklen_t addrlen;
int s;
struct svr* svr = global_svr;
struct sslconn* sc;
if(err != NETEVENT_NOERROR) {
log_err("error %d on remote_accept_callback", err);
return 0;
}
/* perform the accept */
s = comm_point_perform_accept(c, &addr, &addrlen);
if(s == -1)
return 0;
/* create new commpoint unless we are servicing already */
if(svr->active >= svr->max_active) {
log_warn("drop incoming remote control: too many connections");
close_exit:
#ifndef USE_WINSOCK
close(s);
#else
closesocket(s);
#endif
return 0;
}
/* setup commpoint to service the remote control command */
sc = (struct sslconn*)calloc(1, sizeof(*sc));
if(!sc) {
log_err("out of memory");
goto close_exit;
}
/* start in reading state */
sc->c = comm_point_create_raw(svr->base, s, 0, &control_callback, sc);
if(!sc->c) {
log_err("out of memory");
free(sc);
goto close_exit;
}
log_addr(VERB_QUERY, "new control connection from", &addr, addrlen);
sc->c->do_not_close = 0;
/* no timeout on the connection: the panel stays connected for long */
memcpy(&sc->c->repinfo.addr, &addr, addrlen);
sc->c->repinfo.addrlen = addrlen;
sc->shake_state = rc_hs_read;
sc->ssl = SSL_new(svr->ctx);
if(!sc->ssl) {
log_crypto_err("could not SSL_new");
comm_point_delete(sc->c);
free(sc);
return 0;
}
SSL_set_accept_state(sc->ssl);
(void)SSL_set_mode(sc->ssl, SSL_MODE_AUTO_RETRY);
if(!SSL_set_fd(sc->ssl, s)) {
log_crypto_err("could not SSL_set_fd");
SSL_free(sc->ssl);
comm_point_delete(sc->c);
free(sc);
return 0;
}
#ifdef USE_WINSOCK
comm_point_tcp_win_bio_cb(sc->c, sc->ssl);
#endif
sc->buffer = ldns_buffer_new(65536);
if(!sc->buffer) {
log_err("out of memory");
SSL_free(sc->ssl);
comm_point_delete(sc->c);
free(sc);
return 0;
}
sc->next = svr->busy_list;
svr->busy_list = sc;
svr->active ++;
/* perform the first nonblocking read already, for windows,
* so it can return wouldblock. could be faster too. */
(void)control_callback(sc->c, sc, NETEVENT_NOERROR, NULL);
return 0;
}
int control_callback(struct comm_point* c, void* arg, int err,
struct comm_reply* ATTR_UNUSED(reply_info))
{
struct sslconn* s = (struct sslconn*)arg;
int r;
if(err != NETEVENT_NOERROR) {
if(err==NETEVENT_TIMEOUT)
log_err("remote control timed out");
sslconn_delete(s);
return 0;
}
/* (continue to) setup the SSL connection */
if(s->shake_state == rc_hs_read || s->shake_state == rc_hs_write) {
ERR_clear_error();
r = SSL_do_handshake(s->ssl);
if(r != 1) {
int r2 = SSL_get_error(s->ssl, r);
if(r2 == SSL_ERROR_WANT_READ) {
if(s->shake_state == rc_hs_read) {
/* try again later */
return 0;
}
s->shake_state = rc_hs_read;
comm_point_listen_for_rw(c, 1, 0);
return 0;
} else if(r2 == SSL_ERROR_WANT_WRITE) {
if(s->shake_state == rc_hs_write) {
/* try again later */
return 0;
}
s->shake_state = rc_hs_write;
comm_point_listen_for_rw(c, 0, 1);
return 0;
} else if(r2 == SSL_ERROR_SYSCALL) {
if(ERR_peek_error()) {
char errbuf[128];
ERR_error_string_n(ERR_get_error(),
errbuf, sizeof(errbuf));
log_err("ssl_handshake: %s", errbuf);
} else if(r == 0) {
log_err("ssl_handshake EOF violation");
} else if(r == -1) {
#ifdef USE_WINSOCK
log_err("ssl_handshake syscall: "
"%s, wsa: %s", strerror(errno),
wsa_strerror(WSAGetLastError()));
#else
log_err("ssl_handshake syscall: %s",
strerror(errno));
#endif
} else log_err("ssl_handshake syscall ret %d",
r);
sslconn_delete(s);
return 0;
} else {
if(r == 0)
log_err("remote control connection closed prematurely");
log_addr(1, "failed connection from",
&s->c->repinfo.addr, s->c->repinfo.addrlen);
log_crypto_err("remote control failed ssl");
sslconn_delete(s);
return 0;
}
}
/* once handshake has completed, check authentication */
if(SSL_get_verify_result(s->ssl) == X509_V_OK) {
X509* x = SSL_get_peer_certificate(s->ssl);
if(!x) {
verbose(VERB_DETAIL, "remote control connection "
"provided no client certificate");
sslconn_delete(s);
return 0;
}
verbose(VERB_ALGO, "remote control connection authenticated");
X509_free(x);
} else {
verbose(VERB_DETAIL, "remote control connection failed to "
"authenticate with client certificate");
sslconn_delete(s);
return 0;
}
/* set to read state */
s->line_state = command_read;
if(s->shake_state == rc_hs_write)
comm_point_listen_for_rw(c, 1, 0);
s->shake_state = rc_hs_none;
ldns_buffer_clear(s->buffer);
} else if(s->shake_state == rc_hs_want_write) {
/* we have satisfied the condition that the socket is
* writable, remove the handshake state, and continue */
comm_point_listen_for_rw(c, 1, 0); /* back to reading */
s->shake_state = rc_hs_none;
} else if(s->shake_state == rc_hs_want_read) {
/* we have satisfied the condition that the socket is
* readable, remove the handshake state, and continue */
comm_point_listen_for_rw(c, 1, 1); /* back to writing */
s->shake_state = rc_hs_none;
} else if(s->shake_state == rc_hs_shutdown) {
sslconn_shutdown(s);
}
if(s->line_state == command_read) {
if(!sslconn_readline(s))
return 0;
/* we are done handle it */
sslconn_command(s);
} else if(s->line_state == persist_read) {
do {
if(!sslconn_readline(s))
return 0;
/* we are done handle it */
sslconn_persist_command(s);
/* there may be more to read in the same SSL packet */
} while(SSL_pending(s->ssl) != 0);
} else if(s->line_state == persist_write) {
if(sslconn_checkclose(s))
return 0;
if(!sslconn_write(s))
return 0;
if(s->fetch_another_update) {
s->fetch_another_update = 0;
send_results_to_con(global_svr, s);
return 0;
}
/* nothing more to write */
if(s->close_me) {
sslconn_shutdown(s);
return 0;
}
comm_point_listen_for_rw(c, 1, 0);
s->line_state = persist_write_checkclose;
} else if(s->line_state == persist_write_checkclose) {
(void)sslconn_checkclose(s);
}
return 0;
}
static int sslconn_readline(struct sslconn* sc)
{
int r;
while(ldns_buffer_available(sc->buffer, 1)) {
ERR_clear_error();
if((r=SSL_read(sc->ssl, ldns_buffer_current(sc->buffer), 1))
<= 0) {
int want = SSL_get_error(sc->ssl, r);
if(want == SSL_ERROR_ZERO_RETURN) {
sslconn_shutdown(sc);
return 0;
} else if(want == SSL_ERROR_WANT_READ) {
return 0;
} else if(want == SSL_ERROR_WANT_WRITE) {
sc->shake_state = rc_hs_want_write;
comm_point_listen_for_rw(sc->c, 0, 1);
return 0;
} else if(want == SSL_ERROR_SYSCALL) {
if(ERR_peek_error()) {
char errbuf[128];
ERR_error_string_n(ERR_get_error(),
errbuf, sizeof(errbuf));
log_err("ssl_read: %s", errbuf);
} else if(r == 0) {
log_err("ssl_read EOF violation");
} else if(r == -1) {
#ifdef USE_WINSOCK
int wsar = WSAGetLastError();
/* conn reset common at restarts */
if(wsar == WSAECONNRESET)
verbose(VERB_ALGO,
"ssl_read syscall: %s",
wsa_strerror(wsar));
else log_err("ssl_read syscall: "
"%s, wsa: %s", strerror(errno),
wsa_strerror(wsar));
#else
log_err("ssl_read syscall: %s",
strerror(errno));
#endif
} else log_err("ssl_read syscall ret %d", r);
sslconn_delete(sc);
return 0;
}
log_crypto_err("could not SSL_read");
sslconn_delete(sc);
return 0;
}
if(ldns_buffer_current(sc->buffer)[0] == '\n') {
/* return string without \n */
ldns_buffer_write_u8(sc->buffer, 0);
ldns_buffer_flip(sc->buffer);
return 1;
}
ldns_buffer_skip(sc->buffer, 1);
}
log_err("ssl readline too long");
sslconn_delete(sc);
return 0;
}
static int sslconn_write(struct sslconn* sc)
{
int r;
/* ignore return, if fails we may simply block */
(void)SSL_set_mode(sc->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
while(ldns_buffer_remaining(sc->buffer)>0) {
ERR_clear_error();
if((r=SSL_write(sc->ssl, ldns_buffer_current(sc->buffer),
(int)ldns_buffer_remaining(sc->buffer)))
<= 0) {
int want = SSL_get_error(sc->ssl, r);
if(want == SSL_ERROR_ZERO_RETURN) {
/* the other side has closed the channel */
verbose(VERB_ALGO, "result write closed");
sslconn_delete(sc);
return 0;
} else if(want == SSL_ERROR_WANT_READ) {
sc->shake_state = rc_hs_want_read;
comm_point_listen_for_rw(sc->c, 1, 0);
return 0;
} else if(want == SSL_ERROR_WANT_WRITE) {
return 0;
} else if(want == SSL_ERROR_SYSCALL) {
if(ERR_peek_error()) {
char errbuf[128];
ERR_error_string_n(ERR_get_error(),
errbuf, sizeof(errbuf));
log_err("ssl_write: %s", errbuf);
} else if(r == 0) {
log_err("ssl_write EOF violation");
} else if(r == -1) {
#ifdef USE_WINSOCK
log_err("ssl_write syscall: "
"%s, wsa: %s", strerror(errno),
wsa_strerror(WSAGetLastError()));
#else
log_err("ssl_write syscall: %s",
strerror(errno));
#endif
} else log_err("ssl_write syscall ret %d", r);
sslconn_delete(sc);
return 0;
}
log_crypto_err("could not SSL_write");
/* the other side has closed the channel */
sslconn_delete(sc);
return 0;
}
ldns_buffer_skip(sc->buffer, (ssize_t)r);
}
/* done writing the buffer. */
return 1;
}
static void sslconn_shutdown(struct sslconn* sc)
{
int r = SSL_shutdown(sc->ssl);
if(r > 0) {
sslconn_delete(sc);
} else if(r == 0) {
/* we do not need to get notify from the peer, since we
* close the fd */
sslconn_delete(sc);
} else {
int want = SSL_get_error(sc->ssl, r);
sc->shake_state = rc_hs_shutdown;
if(want == SSL_ERROR_ZERO_RETURN) {
sslconn_delete(sc);
} else if(want == SSL_ERROR_WANT_READ) {
comm_point_listen_for_rw(sc->c, 1, 0);
} else if(want == SSL_ERROR_WANT_WRITE) {
comm_point_listen_for_rw(sc->c, 0, 1);
} else {
log_crypto_err("could not SSL_shutdown");
sslconn_delete(sc);
}
}
}
static int sslconn_checkclose(struct sslconn* sc)
{
int r;
ERR_clear_error();
if((r=SSL_read(sc->ssl, NULL, 0)) <= 0) {
int want = SSL_get_error(sc->ssl, r);
if(want == SSL_ERROR_ZERO_RETURN) {
verbose(VERB_ALGO, "checked channel closed otherside");
sslconn_shutdown(sc);
return 1;
} else if(want == SSL_ERROR_WANT_READ) {
return 0;
} else if(want == SSL_ERROR_WANT_WRITE) {
sc->shake_state = rc_hs_want_write;
comm_point_listen_for_rw(sc->c, 0, 1);
return 0;
} else if(want == SSL_ERROR_SYSCALL) {
if(ERR_peek_error()) {
char errbuf[128];
ERR_error_string_n(ERR_get_error(),
errbuf, sizeof(errbuf));
log_err("checkclose ssl_read: %s", errbuf);
} else if(r == 0) {
log_err("checkclose ssl_read EOF violation");
} else if(r == -1) {
#ifdef USE_WINSOCK
int wsar = WSAGetLastError();
/* connreset common at restart of system */
if(wsar == WSAECONNRESET)
verbose(VERB_ALGO,
"checkclose ssl_read syscall: %s",
wsa_strerror(wsar));
else log_err("checkclose ssl_read syscall: "
"%s, wsa: %s", strerror(errno),
wsa_strerror(WSAGetLastError()));
#else
log_err("checkclose ssl_read syscall: %s",
strerror(errno));
#endif
} else log_err("checkclose ssl_read syscall ret %d",
r);
sslconn_delete(sc);
return 0;
}
log_crypto_err("checkclose could not SSL_read");
sslconn_delete(sc);
return 1;
}
if(SSL_get_shutdown(sc->ssl)) {
verbose(VERB_ALGO, "checked channel closed");
sslconn_delete(sc);
return 1;
}
return 0;
}
static void persist_cmd_insecure(int val)
{
struct svr* svr = global_svr;
int was_insecure = svr->insecure_state;
svr->insecure_state = val;
/* see if we need to change unbound's settings */
if(svr->res_state == res_dark) {
if(!was_insecure && val) {
/* set resolv.conf to the DHCP IP list */
hook_resolv_iplist(svr->cfg, svr->probes);
} else if(was_insecure && !val) {
/* set resolv.conf to 127.0.0.1 */
hook_resolv_localhost(svr->cfg);
}
} else {
/* no need for insecure; robustness, in case some delayed
* command arrives when we have reprobed again */
if(!svr->forced_insecure)
svr->insecure_state = 0;
}
svr_send_results(svr);
}
void cmd_reprobe(void)
{
char buf[10240];
char* now = buf;
size_t left = sizeof(buf);
struct probe_ip* p;
buf[0]=0; /* safe, robust */
for(p = global_svr->probes; p; p = p->next) {
if(probe_is_cache(p)) {
size_t len;
if(left < strlen(p->name)+3)
break; /* no space for more */
snprintf(now, left, "%s%s",
(now==buf)?"":" ", p->name);
len = strlen(now);
left -= len;
now += len;
}
}
probe_start(buf);
}
static void handle_hotspot_signon_cmd(struct svr* svr)
{
verbose(VERB_OPS, "state dark forced_insecure");
probe_setup_hotspot_signon(svr);
svr_send_results(svr);
}
static void handle_skip_http_cmd(void)
{
verbose(VERB_OPS, "state skip_http and reprobe");
global_svr->skip_http = 1;
cmd_reprobe();
}
static void sslconn_persist_command(struct sslconn* sc)
{
char* str = (char*)ldns_buffer_begin(sc->buffer);
while(*str == ' ')
str++;
verbose(VERB_ALGO, "persist-channel command: %s", str);
if(*str == 0) {
/* ignore empty lines */
} else if(strcmp(str, "insecure yes") == 0) {
persist_cmd_insecure(1);
} else if(strcmp(str, "insecure no") == 0) {
persist_cmd_insecure(0);
} else if(strcmp(str, "reprobe") == 0) {
global_svr->forced_insecure = 0;
global_svr->http_insecure = 0;
cmd_reprobe();
} else if(strcmp(str, "skip_http") == 0) {
handle_skip_http_cmd();
} else if(strcmp(str, "hotspot_signon") == 0) {
handle_hotspot_signon_cmd(global_svr);
} else if(strcmp(str, "update_cancel") == 0) {
selfupdate_userokay(global_svr->update, 0);
} else if(strcmp(str, "update_ok") == 0) {
selfupdate_userokay(global_svr->update, 1);
} else {
log_err("unknown command from panel: %s", str);
}
/* and ready to read the next command */
ldns_buffer_clear(sc->buffer);
}
static void handle_submit(char* ips)
{
/* start probing the servers */
probe_start(ips);
}
/** append update signal to buffer to send */
static void
append_update_to_con(struct sslconn* s, char* version_available)
{
ldns_buffer_printf(s->buffer, "update %s\n%s\n\n", PACKAGE_VERSION,
version_available);
}
static void
send_results_to_con(struct svr* svr, struct sslconn* s)
{
struct probe_ip* p;
char at[32];
int numcache = 0, unfinished = 0;
ldns_buffer_clear(s->buffer);
if(svr->probetime == 0)
ldns_buffer_printf(s->buffer, "at (no probe performed)\n");
else if(strftime(at, sizeof(at), "%Y-%m-%d %H:%M:%S",
localtime(&svr->probetime)))
ldns_buffer_printf(s->buffer, "at %s\n", at);
for(p=svr->probes; p; p=p->next) {
if(probe_is_cache(p))
numcache++;
if(!p->finished) {
unfinished++;
continue;
}
if(p->to_http) {
if(p->host_c) {
ldns_buffer_printf(s->buffer, "%s %s %s from %s: %s %s\n",
"addr", p->host_c->qname,
p->http_ip6?"AAAA":"A", p->name,
p->works?"OK":"error", p->reason?p->reason:"");
} else
ldns_buffer_printf(s->buffer, "%s %s (%s): %s %s\n",
"http", p->http_desc, p->name,
p->works?"OK":"error", p->reason?p->reason:"");
} else if(p->dnstcp)
ldns_buffer_printf(s->buffer, "%s%d %s: %s %s\n",
p->ssldns?"ssl":"tcp", p->port, p->name,
p->works?"OK":"error", p->reason?p->reason:"");
else
ldns_buffer_printf(s->buffer, "%s %s: %s %s\n",
p->to_auth?"authority":"cache", p->name,
p->works?"OK":"error", p->reason?p->reason:"");
}
if(unfinished)
ldns_buffer_printf(s->buffer, "probe is in progress\n");
else if(!numcache)
ldns_buffer_printf(s->buffer, "no cache: no DNS servers have been supplied via DHCP\n");
ldns_buffer_printf(s->buffer, "state: %s %s%s%s\n",
svr->res_state==res_cache?"cache":(
svr->res_state==res_tcp?"tcp":(
svr->res_state==res_ssl?"ssl":(
svr->res_state==res_auth?"auth":(
svr->res_state==res_disconn?"disconnected":"nodnssec")))),
svr->insecure_state?"insecure_mode":"secure",
svr->forced_insecure?" forced_insecure":"",
svr->http_insecure?" http_insecure":""
);
ldns_buffer_printf(s->buffer, "\n");
if(svr->update && svr->update->update_available &&
!svr->update->user_replied) {
log_info("append_update signal");
append_update_to_con(s, svr->update->version_available);
}
ldns_buffer_flip(s->buffer);
comm_point_listen_for_rw(s->c, 1, 1);
s->line_state = persist_write;
}
void svr_signal_update(struct svr* svr, char* version_available)
{
/* write stop to all connected panels */
struct sslconn* s;
for(s=svr->busy_list; s; s=s->next) {
if(s->line_state == persist_write) {
/* busy with last results, fetch update later */
s->fetch_another_update=1;
}
if(s->line_state == persist_write_checkclose) {
ldns_buffer_clear(s->buffer);
append_update_to_con(s, version_available);
ldns_buffer_flip(s->buffer);
comm_point_listen_for_rw(s->c, 1, 1);
s->line_state = persist_write;
}
}
}
static void handle_results_cmd(struct sslconn* sc)
{
/* turn into persist write with results. */
ldns_buffer_clear(sc->buffer);
ldns_buffer_flip(sc->buffer);
/* must listen for close of connection: reading */
comm_point_listen_for_rw(sc->c, 1, 0);
sc->line_state = persist_write_checkclose;
/* feed it the first results (if any) */
send_results_to_con(global_svr, sc);
}
static void handle_status_cmd(struct sslconn* sc)
{
sc->close_me = 1;
handle_results_cmd(sc);
}
static void handle_printclose(struct sslconn* sc, char* str)
{
/* write and then close */
sc->close_me = 1;
comm_point_listen_for_rw(sc->c, 1, 1);
sc->line_state = persist_write;
/* enter contents */
ldns_buffer_clear(sc->buffer);
ldns_buffer_printf(sc->buffer, "%s\n", str);
ldns_buffer_flip(sc->buffer);
}
static void handle_cmdtray_cmd(struct sslconn* sc)
{
#ifdef HOOKS_OSX
/* OSX has messed up resolv.conf after relogin */
restore_resolv_osx(global_svr->cfg);
#endif
/* turn into persist read */
ldns_buffer_clear(sc->buffer);
comm_point_listen_for_rw(sc->c, 1, 0);
sc->line_state = persist_read;
}
static void handle_unsafe_cmd(struct sslconn* sc)
{
probe_unsafe_test();
sslconn_shutdown(sc);
}
static void handle_test_tcp_cmd(struct sslconn* sc)
{
probe_tcp_test();
sslconn_shutdown(sc);
}
static void handle_test_ssl_cmd(struct sslconn* sc)
{
probe_ssl_test();
sslconn_shutdown(sc);
}
static void handle_test_http_cmd(struct sslconn* sc)
{
probe_http_test();
sslconn_shutdown(sc);
}
static void handle_test_update_cmd(struct sslconn* sc)
{
global_svr->update->test_flag = 1;
global_svr->update_desired = 1;
sslconn_shutdown(sc);
}
static void handle_stoppanels_cmd(struct sslconn* sc)
{
/* write stop to all connected panels */
const char* stopcmd = "stop\n";
struct sslconn* s;
for(s=global_svr->busy_list; s; s=s->next) {
/* skip non persistent-write connections */
if(s->line_state != persist_write_checkclose &&
s->line_state != persist_write)
continue;
(void)SSL_set_mode(s->ssl, SSL_MODE_AUTO_RETRY);
if(SSL_get_fd(s->ssl) != -1) {
#ifdef USE_WINSOCK
/* to be able to set it back to blocking mode
* we have to remove the EventSelect on it */
if(WSAEventSelect(SSL_get_fd(s->ssl), NULL, 0)!=0)
log_err("WSAEventSelect disable: %s",
wsa_strerror(WSAGetLastError()));
#endif
fd_set_block(SSL_get_fd(s->ssl));
}
if(s->line_state == persist_write) {
/* busy with last results, blocking write them */
if(SSL_write(s->ssl, ldns_buffer_current(s->buffer),
(int)ldns_buffer_remaining(s->buffer)) < 0)
log_crypto_err("cannot SSL_write remainder");
}
/* blocking write the stop command */
if(SSL_write(s->ssl, stopcmd, (int)strlen(stopcmd)) < 0)
log_crypto_err("cannot SSL_write panel stop");
if(!SSL_get_shutdown(s->ssl)) {
if(SSL_shutdown(s->ssl) == 0)
SSL_shutdown(s->ssl); /* again to wait */
}
/* it will be closed now */
if(SSL_get_fd(s->ssl) != -1)
fd_set_nonblock(SSL_get_fd(s->ssl));
comm_point_listen_for_rw(s->c, 1, 0);
s->line_state = persist_write_checkclose;
}
/* wait until they all stopped, then stop commanding connection */
sslconn_shutdown(sc);
}
static void sslconn_command(struct sslconn* sc)
{
char header[10];
char* str = (char*)ldns_buffer_begin(sc->buffer);
snprintf(header, sizeof(header), "DNSTRIG%d ", CONTROL_VERSION);
if(strncmp(str, header, strlen(header)) != 0) {
log_err("bad version in control connection");
sslconn_delete(sc);
return;
}
str += strlen(header);
while(*str == ' ')
str++;
verbose(VERB_ALGO, "command: %s", str);
if(strncmp(str, "submit", 6) == 0) {
handle_submit(str+6);
sslconn_shutdown(sc);
} else if(strncmp(str, "reprobe", 7) == 0) {
global_svr->forced_insecure = 0;
global_svr->http_insecure = 0;
cmd_reprobe();
sslconn_shutdown(sc);
} else if(strncmp(str, "skip_http", 9) == 0) {
handle_skip_http_cmd();
sslconn_shutdown(sc);
} else if(strncmp(str, "hotspot_signon", 14) == 0) {
handle_hotspot_signon_cmd(global_svr);
sslconn_shutdown(sc);
} else if(strncmp(str, "results", 7) == 0) {
handle_results_cmd(sc);
} else if(strncmp(str, "status", 7) == 0) {
handle_status_cmd(sc);
} else if(strncmp(str, "cmdtray", 7) == 0) {
handle_cmdtray_cmd(sc);
} else if(strncmp(str, "unsafe", 6) == 0) {
handle_unsafe_cmd(sc);
} else if(strncmp(str, "test_tcp", 8) == 0) {
handle_test_tcp_cmd(sc);
} else if(strncmp(str, "test_ssl", 8) == 0) {
handle_test_ssl_cmd(sc);
} else if(strncmp(str, "test_http", 8) == 0) {
handle_test_http_cmd(sc);
} else if(strncmp(str, "test_update", 11) == 0) {
handle_test_update_cmd(sc);
} else if(strncmp(str, "stoppanels", 10) == 0) {
handle_stoppanels_cmd(sc);
} else if(strncmp(str, "stop", 4) == 0) {
comm_base_exit(global_svr->base);
sslconn_shutdown(sc);
} else {
verbose(VERB_DETAIL, "unknown command: %s", str);
handle_printclose(sc, "error unknown command");
}
}
void svr_send_results(struct svr* svr)
{
struct sslconn* s;
for(s=svr->busy_list; s; s=s->next) {
if(s->line_state == persist_write) {
/* busy with last results, fetch update later */
s->fetch_another_update=1;
}
if(s->line_state == persist_write_checkclose) {
send_results_to_con(svr, s);
}
}
}
void svr_retry_callback(void* arg)
{
struct svr* svr = (struct svr*)arg;
if(!svr->retry_timer_enabled) {
comm_timer_disable(svr->retry_timer);
return;
}
verbose(VERB_ALGO, "retry timeout");
cmd_reprobe();
}
static void svr_retry_setit(struct svr* svr)
{
struct timeval tv;
if(svr->retry_timer_count < RETRY_TIMER_COUNT_MAX)
verbose(VERB_ALGO, "retry in %d seconds (try nr %d)", svr->retry_timer_timeout, svr->retry_timer_count);
else verbose(VERB_ALGO, "retry in %d seconds", svr->retry_timer_timeout);
tv.tv_sec = svr->retry_timer_timeout;
tv.tv_usec = 0;
comm_timer_set(svr->retry_timer, &tv);
}
static void svr_retry_start(struct svr* svr, int http_mode)
{
svr->retry_timer_timeout = RETRY_TIMER_START;
if(http_mode)
svr->retry_timer_count = 1;
else svr->retry_timer_count = RETRY_TIMER_COUNT_MAX;
svr->retry_timer_enabled = 1;
svr_retry_setit(svr);
}
void svr_retry_timer_next(int http_mode)
{
struct svr* svr = global_svr;
if(!svr->retry_timer_enabled) {
svr_retry_start(svr, http_mode);
return;
}
if(svr->retry_timer_count < RETRY_TIMER_COUNT_MAX) {
svr->retry_timer_count++;
} else {
svr->retry_timer_timeout *= 2;
if(svr->retry_timer_timeout > RETRY_TIMER_MAX)
svr->retry_timer_timeout = RETRY_TIMER_MAX;
}
svr_retry_setit(svr);
}
void svr_retry_timer_stop(void)
{
struct svr* svr = global_svr;
if(!svr->retry_timer_enabled)
return;
svr->retry_timer_enabled = 0;
comm_timer_disable(svr->retry_timer);
}
void svr_tcp_timer_stop(void)
{
struct svr* svr = global_svr;
comm_timer_disable(svr->retry_timer);
}
void svr_tcp_timer_enable(void)
{
struct svr* svr = global_svr;
struct timeval tv;
if(svr->tcp_timer_used)
return;
verbose(VERB_ALGO, "retry dnstcp in %d seconds", SVR_TCP_RETRY);
tv.tv_sec = SVR_TCP_RETRY;
tv.tv_usec = 0;
comm_timer_set(svr->tcp_timer, &tv);
}
void svr_tcp_callback(void* arg)
{
/* we do this probe because some 20 seconds after login, more
* ports may open and this can alleviate traffic on the open
* recursors */
struct svr* svr = (struct svr*)arg;
verbose(VERB_ALGO, "retry dnstcp timeout");
comm_timer_disable(svr->tcp_timer);
if(svr->res_state == res_tcp || svr->res_state == res_ssl) {
svr->tcp_timer_used = 1;
cmd_reprobe();
}
}
void svr_check_update(struct svr* svr)
{
if(svr->update_desired && !svr->insecure_state && !svr->forced_insecure
&& svr->res_state != res_dark && svr->res_state != res_disconn)
{
selfupdate_start(svr->update);
}
}