int
dnsproc(int nfd)
{
	int		 rc, cc;
	char		*look, *last;
	struct addr	 v[MAX_SERVERS_DNS];
	long		 lval;
	size_t		 i;
	ssize_t		 vsz;
	enum dnsop	 op;

	rc = 0;
	look = last = NULL;
	vsz = 0;

	/*
	 * Why don't we chroot() here?
	 * On OpenBSD, the pledge(2) takes care of our constraining the
	 * environment to DNS resolution only, so the chroot(2) is
	 * unnecessary.
	 * On Mac OS X, we can't chroot(2): we'd need to have an mdns
	 * responder thing in each jail.
	 * On Linux, forget it.  getaddrinfo(2) pulls on all sorts of
	 * mystery meat.
	 */

	if ( ! sandbox_before())
		goto out;
	else if ( ! dropprivs())
		goto out;
	else if ( ! sandbox_after()) 
		goto out;

	/*
	 * This is simple: just loop on a request operation, and each
	 * time we write back zero or more entries.
	 * Also do a simple trick and cache the last lookup.
	 */

	for (;;) {
		op = DNS__MAX;
		if (0 == (lval = readop(nfd, COMM_DNS)))
			op = DNS_STOP;
		else if (DNS_LOOKUP == lval)
			op = lval;

		if (DNS__MAX == op) {
			warnx("unknown operation from netproc");
			goto out;
		} else if (DNS_STOP == op)
			break;

		if (NULL == (look = readstr(nfd, COMM_DNSQ)))
			goto out;

		/* 
		 * Check if we're asked to repeat the lookup.
		 * If not, request it from host_dns().
		 */

		if (NULL == last || strcmp(look, last)) {
			if ((vsz = host_dns(look, v)) < 0)
				goto out;

			free(last);
			last = look;
			look = NULL;
		} else {
			doddbg("%s: cached", look);
			free(look);
			look = NULL;
		}

		if (0 == (cc = writeop(nfd, COMM_DNSLEN, vsz)))
			break;
		else if (cc < 0)
			goto out;
		for (i = 0; i < (size_t)vsz; i++) { 
			if (writeop(nfd, COMM_DNSF, v[i].family) <= 0)
				goto out;
			if (writestr(nfd, COMM_DNSA, v[i].ip) <= 0)
				goto out;
		}
	}

	rc = 1;
out:
	close(nfd);
	free(look);
	free(last);
	return(rc);
}
int
certproc(int netsock, int filesock)
{
	char		*csr, *chain, *url;
	unsigned char	*csrcp, *chaincp;
	size_t		 csrsz, chainsz;
	int		 i, rc, idx, cc;
	enum certop	 op;
	long		 lval;
	X509		*x, *chainx;
	X509_EXTENSION	*ext;
	X509V3_EXT_METHOD *method;
	void		*entries;
	STACK_OF(CONF_VALUE) *val;
	CONF_VALUE	*nval;

	ext = NULL;
	idx = -1;
	method = NULL;
	chain = csr = url = NULL;
	rc = 0;
	x = chainx = NULL;

	/* File-system and sandbox jailing. */

	if ( ! sandbox_before())
		goto out;

	ERR_load_crypto_strings();

	if ( ! dropfs(PATH_VAR_EMPTY))
		goto out;
	else if ( ! dropprivs())
		goto out;
	else if ( ! sandbox_after())
		goto out;

	/* Read what the netproc wants us to do. */

	op = CERT__MAX;
	if (0 == (lval = readop(netsock, COMM_CSR_OP)))
		op = CERT_STOP;
	else if (CERT_REVOKE == lval || CERT_UPDATE == lval)
		op = lval;

	if (CERT_STOP == op) {
		rc = 1;
		goto out;
	} else if (CERT__MAX == op) {
		warnx("unknown operation from netproc");
		goto out;
	}

	/* 
	 * Pass revocation right through to fileproc. 
	 * If the reader is terminated, ignore it.
	 */

	if (CERT_REVOKE == op) {
		if (writeop(filesock, COMM_CHAIN_OP, FILE_REMOVE) >= 0)
			rc = 1;
		goto out;
	}

	/*
	 * Wait until we receive the DER encoded (signed) certificate
	 * from the network process.
	 * Then convert the DER encoding into an X509 certificate.
	 */

	if (NULL == (csr = readbuf(netsock, COMM_CSR, &csrsz)))
		goto out;

	csrcp = (u_char *)csr;
	x = d2i_X509(NULL, (const u_char **)&csrcp, csrsz);
	if (NULL == x) {
		warnx("d2i_X509");
		goto out;
	}

	/*
	 * Extract the CA Issuers from its NID.
	 * TODO: I have no idea what I'm doing.
	 */

	idx = X509_get_ext_by_NID(x, NID_info_access, idx);
	if (idx >= 0 && NULL != (ext = X509_get_ext(x, idx)))
		method = (X509V3_EXT_METHOD *)X509V3_EXT_get(ext);

	entries = X509_get_ext_d2i(x, NID_info_access, 0, 0);
	if (NULL != method && NULL != entries) {
		val = method->i2v(method, entries, 0);
		for (i = 0; i < sk_CONF_VALUE_num(val); i++) {
			nval = sk_CONF_VALUE_value(val, i);
			if (strcmp(nval->name, "CA Issuers - URI"))
				continue;
			url = strdup(nval->value);
			if (NULL == url) {
				warn("strdup");
				goto out;
			}
			break;
		}
	}

	if (NULL == url) {
		warnx("no CA issuer registered with certificate");
		goto out;
	}

	/* Write the CA issuer to the netsock. */

	if (writestr(netsock, COMM_ISSUER, url) <= 0)
		goto out;

	/* Read the full-chain back from the netsock. */

	if (NULL == (chain = readbuf(netsock, COMM_CHAIN, &chainsz)))
		goto out;

	/*
	 * Then check if the chain is PEM-encoded by looking to see if
	 * it begins with the PEM marker.
	 * If so, ship it as-is; otherwise, convert to a PEM encoded
	 * buffer and ship that.
	 * FIXME: if PEM, re-parse it.
	 */

	if (chainsz <= strlen(MARKER) ||
	    strncmp(chain, MARKER, strlen(MARKER))) {
		chaincp = (u_char *)chain;
		chainx = d2i_X509(NULL, 
			(const u_char **)&chaincp, chainsz);
		if (NULL == chainx) {
			warnx("d2i_X509");
			goto out;
		}
		free(chain);
		if (NULL == (chain = x509buf(chainx, &chainsz)))
			goto out;
	} 

	/* Allow reader termination to just push us out. */

	if (0 == (cc = writeop(filesock, COMM_CHAIN_OP, FILE_CREATE)))
		rc = 1;
	if (cc <= 0)
		goto out;
	if (0 == (cc = writebuf(filesock, COMM_CHAIN, chain, chainsz)))
		rc = 1;
	if (cc <= 0)
		goto out;

	/* 
	 * Next, convert the X509 to a buffer and send that. 
	 * Reader failure doesn't change anything.
	 */

	free(chain);
	if (NULL == (chain = x509buf(x, &chainsz)))
		goto out;
	if (writebuf(filesock, COMM_CSR, chain, chainsz) < 0)
		goto out;

	rc = 1;
out:
	close(netsock);
	close(filesock);
	if (NULL != x)
		X509_free(x);
	if (NULL != chainx)
		X509_free(chainx);
	free(csr);
	free(url);
	free(chain);
	ERR_print_errors_fp(stderr);
	ERR_free_strings();
	return(rc);
}
Beispiel #3
0
int main( int argc, char *argv[] ) {
    sigset_t newmask;
    char c;
    extern char *optarg;
    extern int optind, opterr, optopt;
    char *cfgfile = HTUN_DEFAULT_CFGFILE;
    char *tunfile = NULL;
    char *logfile = NULL;
    unsigned short port=0;
    int foreground = 0;
    int dont_route = 0;
    int config_test_only = 0;
    int debug = 0;
    unsigned int logflags = 0;

    dropprivs("");

    while( (c=getopt(argc, argv, "rdfc:vht:l:p:o")) != -1 ) {
        switch(c) {
            case 'r':
                dont_route = 1;
                break;
/* #ifdef _DEBUG */
            case 'd':
                debug = 1;
                break;
/* #endif */
            case 'o':
                debug = 1;
                config_test_only = 1;
                break;
            case 'f':
                foreground = 1;
                break;
            case 'c':
                cfgfile = optarg;
                break;
            case 'v':
                fprintf( stderr, 
                  "HTun " VERSION "\n"
                  "(c) 2002  Moshe Jacobson <*****@*****.**>\n"
                  "      and Ola Nordstrom <*****@*****.**>\n"
                  "This program is distributed under the GNU General Public License.\n"
                  "for details, see the LICENSE file packaged with htun.\n");
                return EXIT_SUCCESS;
            case 't':
                tunfile = optarg;
                break;
            case 'l':
                logfile = optarg;
                break;
            case 'p':
                port = htons((unsigned short)strtoul(optarg,NULL,0));
                if(!port) {
                    fprintf( stderr, "Invalid port specified: %s.\n", 
                            argv[optind] );
                    return EXIT_FAILURE;
                }
                break;
            case 'h':
                usage();
                return EXIT_SUCCESS;
            default:
                usage();
                return EXIT_FAILURE;
        }
    }
                
    /* Read the config */
    if( (config=read_config(cfgfile)) == NULL ) {
        fprintf( stderr, "Fatal: Reading cfgfile \"%s\": %s\n",
                cfgfile, strerror(errno) );
        return EXIT_FAILURE;
    }

    if( config_test_only == 1 ) {
        log = log_open("-", LOG_STDERR|LOG_NODATE|LOG_DEBUG|LOG_NOLVL|LOG_FUNC);
        print_config(config);
        log_close(log);
        return EXIT_SUCCESS;
    }

    /* Now override config values with cmdline values */
    strcpy(config->cfgfile,cfgfile);
    if( tunfile ) strncpy(config->tunfile,tunfile,PATH_MAX);
    if( logfile ) strncpy(config->logfile,logfile,PATH_MAX);
    if( port ) {
        if( config->is_server )
            config->u.s.server_ports[0] = htons(port);
        else
            config->u.c.proxy_port = (port);
    }
    if( dont_route ) config->u.c.do_routing = 0;
    if( debug ) config->debug = 1;
    

    /* Open the log file */
    if( config->debug ) logflags |= LOG_DEBUG;
    logflags |= LOG_FUNC;
    getprivs("opening logfile");
    if( !(log=log_open( config->logfile, logflags )) ) {
        fprintf( stderr, "Warning: Could not open logfile %s: %s\n",
                config->logfile, strerror(errno) );
    }
    dropprivs("logfile opened");

    /* Daemonize unless -f or -l - have been specified */
    if( !foreground && strcmp(config->logfile,"-") ) daemonize();
    
    lprintf( log, INFO, "HTun " VERSION " started." );

    init_signames();

    /* Ignore SIGPIPE. We will use errno=EPIPE instead. */
    signal(SIGPIPE,SIG_IGN);
    signal(SIGWINCH,SIG_IGN);

    /* Mask TERM, HUP, INT, and USR1 */
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGINT);
    sigaddset(&newmask, SIGHUP);
    sigaddset(&newmask, SIGTERM);
    sigaddset(&newmask, SIGUSR1);
    sigaddset(&newmask, SIGUSR2);
    sigaddset(&newmask, SIGALRM);
    sigaddset(&newmask, SIGCONT);
    sigaddset(&newmask, SIGTSTP);
    sigaddset(&newmask, SIGWINCH);
    sigprocmask( SIG_BLOCK, &newmask, NULL );

    return config->is_server ? server_main() : client_main();
}
/*
 * Here we communicate with the letsencrypt server.
 * For this, we'll need the certificate we want to upload and our
 * account key information.
 */
int
netproc(int kfd, int afd, int Cfd, int cfd, int dfd, int rfd,
	int newacct, int revoke, int staging,
	const char *const *alts, size_t altsz)
{
	int		 rc;
	size_t		 i;
	char		*cert, *thumb, *url;
	struct conn	 c;
	struct capaths	 paths;
	struct chng 	*chngs;
	long		 lval;

	rc = 0;
	memset(&paths, 0, sizeof(struct capaths));
	memset(&c, 0, sizeof(struct conn));
	url = cert = thumb = NULL;
	chngs = NULL;

	/* File-system, user, and sandbox jail. */

	if ( ! sandbox_before())
		goto out;
	else if ( ! dropfs(PATH_VAR_EMPTY))
		goto out;
	else if ( ! dropprivs())
		goto out;
	else if ( ! sandbox_after())
		goto out;

	/*
	 * Wait until the acctproc, keyproc, and revokeproc have started
	 * up and are ready to serve us data.
	 * There's no point in running if these don't work.
	 * Then check whether revokeproc indicates that the certificate
	 * on file (if any) can be updated.
	 */

	if (0 == (lval = readop(afd, COMM_ACCT_STAT))) {
		rc = 1;
		goto out;
	} else if (ACCT_READY != lval) {
		warnx("unknown operation from acctproc");
		goto out;
	}

	if (0 == (lval = readop(kfd, COMM_KEY_STAT))) {
		rc = 1;
		goto out;
	} else if (KEY_READY != lval) {
		warnx("unknown operation from keyproc");
		goto out;
	}

	if (0 == (lval = readop(rfd, COMM_REVOKE_RESP))) {
		rc = 1;
		goto out;
	} else if (REVOKE_EXP != lval && REVOKE_OK != lval) {
		warnx("unknown operation from revokeproc");
		goto out;
	}

	/* If our certificate is up-to-date, return now. */

	if (REVOKE_OK == lval) {
		rc = 1;
		goto out;
	}

	/* Allocate main state. */

	chngs = calloc(altsz, sizeof(struct chng));
	if (NULL == chngs) {
		warn("calloc");
		goto out;
	}

	c.dfd = dfd;
	c.fd = afd;
	c.na = staging ? URL_STAGE_CA : URL_REAL_CA;

	/*
	 * Look up the domain of the ACME server.
	 * We'll use this ourselves instead of having libcurl do the DNS
	 * resolution itself.
	 */
	if ( ! dodirs(&c, c.na, &paths))
		goto out;

	/*
	 * If we're meant to revoke, then wait for revokeproc to send us
	 * the certificate (if it's found at all).
	 * Following that, submit the request to the CA then notify the
	 * certproc, which will in turn notify the fileproc.
	 */

	if (revoke) {
		if (NULL == (cert = readstr(rfd, COMM_CSR)))
			goto out;
		if ( ! dorevoke(&c, paths.revokecert, cert))
			goto out;
		else if (writeop(cfd, COMM_CSR_OP, CERT_REVOKE) > 0)
			rc = 1;
		goto out;
	}

	/* If new, register with the CA server. */

	if (newacct && ! donewreg(&c, &paths))
		goto out;

	/* Pre-authorise all domains with CA server. */

	for (i = 0; i < altsz; i++)
		if ( ! dochngreq(&c, alts[i], &chngs[i], &paths))
			goto out;

	/*
	 * We now have our challenges.
	 * We need to ask the acctproc for the thumbprint.
	 * We'll combine this to the challenge to create our response,
	 * which will be orchestrated by the chngproc.
	 */

	if (writeop(afd, COMM_ACCT, ACCT_THUMBPRINT) <= 0)
		goto out;
	else if (NULL == (thumb = readstr(afd, COMM_THUMB)))
		goto out;

	/* We'll now ask chngproc to build the challenge. */

	for (i = 0; i < altsz; i++) {
		if (writeop(Cfd, COMM_CHNG_OP, CHNG_SYN) <= 0)
			goto out;
		else if (writestr(Cfd, COMM_THUMB, thumb) <= 0)
			goto out;
		else if (writestr(Cfd, COMM_TOK, chngs[i].token) <= 0)
			goto out;

		/* Read that the challenge has been made. */

		if (CHNG_ACK != readop(Cfd, COMM_CHNG_ACK))
			goto out;

		/* Write to the CA that it's ready. */

		if ( ! dochngresp(&c, &chngs[i], thumb))
			goto out;
	}

	/*
	 * We now wait on the ACME server for each domain.
	 * Connect to the server (assume it's the same server) once
	 * every five seconds.
	 */

	for (i = 0; i < altsz; i++) {
		if (1 == chngs[i].status)
			continue;

		if (chngs[i].retry++ >= RETRY_MAX) {
			warnx("%s: too many tries", chngs[i].uri);
			goto out;
		}

		/* Sleep before every attempt. */
		sleep(RETRY_DELAY);
		if ( ! dochngcheck(&c, &chngs[i]))
			goto out;
	}

	/*
	 * Write our acknowledgement that the challenges are over.
	 * The challenge process will remove all of the files.
	 */

	if (writeop(Cfd, COMM_CHNG_OP, CHNG_STOP) <= 0)
		goto out;

	/* Wait to receive the certificate itself. */

	if (NULL == (cert = readstr(kfd, COMM_CERT)))
		goto out;

	/*
	 * Otherwise, submit the CA for signing, download the signed
	 * copy, and ship that into the certificate process for copying.
	 */

	if ( ! docert(&c, paths.newcert, cert))
		goto out;
	else if (writeop(cfd, COMM_CSR_OP, CERT_UPDATE) <= 0)
		goto out;
	else if (writebuf(cfd, COMM_CSR, c.buf.buf, c.buf.sz) <= 0)
		goto out;

	/*
	 * Read back the issuer from the certproc.
	 * Then contact the issuer to get the certificate chain.
	 * Write this chain directly back to the certproc.
	 */

	if (NULL == (url = readstr(cfd, COMM_ISSUER)))
		goto out;
	else if ( ! dofullchain(&c, url))
		goto out;
	else if (writebuf(cfd, COMM_CHAIN, c.buf.buf, c.buf.sz) <= 0)
		goto out;

	rc = 1;
out:
	close(cfd);
	close(kfd);
	close(afd);
	close(Cfd);
	close(dfd);
	close(rfd);
	free(cert);
	free(url);
	free(thumb);
	free(c.buf.buf);
	if (NULL != chngs)
		for (i = 0; i < altsz; i++)
			json_free_challenge(&chngs[i]);
	free(chngs);
	json_free_capaths(&paths);
	return(rc);
}