Commit 40d072cf authored by Leigh B. Stoller's avatar Leigh B. Stoller

A fair amount of cleanup, both of the ssl stuff and of tmcd in general.

Deal with ssl/nossl clients; at Chad's suggestion add a small handshake
tag to ssl enabled tmcc/tmcd which tells tmcd that it needs to enter
full SSL mode. This allows old tmcc to connect to an ssl enabled tmcd,
and still work okay.

I've also ironed out the verification stuff. At the client, we make sure
that the CommonName field of the peer cert maps to the same address that
we connected to (bossnode).

At the server, we check the OU field of the cert (we create the client
certs with the OU field set to the node type; a convention I made up!).
It must match the type of the node, as we get it from the nodes table.
Also check the CommonName to make sure it matches our hostname. This is
by no means bulletproof, but perfection is costly, and we don't have the
money!

Also cleaned up the REDIRECT testmode stuff. Instead of ifdef'ed under
TESTMODE, leave it compiled in all the time, but only allow it from the
local node (where tmcd is running). Mere users will not be able to
access it, but testbed people can use it since they have accounts on the
boss node.
parent 658ee16b
......@@ -20,9 +20,9 @@ TMLIBS = ${OBJDIR}/lib/libtb/libtb.a
#
# For SSL enabled tmcd/tmcc
#
#CFLAGS += -DWITHSSL -DETCDIR='"$(INSTALL_ETCDIR)"'
#CFLAGS += -DWITHSSL -DETCDIR='"$(INSTALL_ETCDIR)"'
#TMLIBS += -lssl -lcrypto
#SSLOBJ = ssl.o
#SSLOBJ = ssl.o
ifeq ($(EVENTSYS),1)
TMCDCFLAGS = `elvin-config --cflags vin4c` \
......
......@@ -2,8 +2,8 @@
* Insert Copyright Here.
*/
#define TBSERVER_PORT 7777
#define MYBUFSIZE 2048
#define TBSERVER_PORT 7777
#define MYBUFSIZE 2048
/*
* As the tmcd changes, incompatable changes with older version of
......@@ -18,6 +18,7 @@
* sure to change it there too!
*
* Note, this is assumed to be an integer. No need for 3.23.479 ...
* NB: See ron/libsetup.pm. That is version 4! I'll merge that in.
*/
#define DEFAULT_VERSION 2
#define CURRENT_VERSION 3
......@@ -12,10 +12,13 @@
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include "decls.h"
......@@ -31,6 +34,11 @@
#define SERVER_CERTFILE "server.pem"
#define CLIENT_CERTFILE "client.pem"
/*
* This is used by tmcd to determine if the connection is ssl or not.
*/
int isssl;
/*
* On the client, we search a couple of dirs for the pem file.
*/
......@@ -46,6 +54,8 @@ static char *clientcertdirs[] = {
static SSL *ssl;
static SSL_CTX *ctx;
static int client = 0;
static char nosslbuf[MYBUFSIZE];
static int nosslbuflen, nosslbufidx;
static void tmcd_sslerror();
static void tmcd_sslprint(const char *fmt, ...);
......@@ -180,24 +190,49 @@ tmcd_client_sslinit(void)
int
tmcd_sslaccept(int sock, struct sockaddr *addr, socklen_t *addrlen)
{
int newsock;
int newsock, cc;
if ((newsock = accept(sock, addr, addrlen)) < 0)
return -1;
/*
* Read the first bit. It indicates whether we need to SSL
* handshake or not.
*/
if ((cc = read(newsock, nosslbuf, sizeof(nosslbuf) - 1)) <= 0) {
error("sslaccept: reading request");
if (cc == 0)
errno = EIO;
return -1;
}
if (strncmp(nosslbuf, SPEAKSSL, strlen(SPEAKSSL))) {
/*
* No ssl. Need to return this data on the next read.
* See below.
*/
isssl = 0;
nosslbuflen = cc;
nosslbufidx = 0;
return newsock;
}
isssl = 1;
nosslbuflen = 0;
if (! (ssl = SSL_new(ctx))) {
tmcd_sslerror();
errno = EIO;
return -1;
}
if (! SSL_set_fd(ssl, newsock)) {
tmcd_sslerror();
errno = EIO;
return -1;
}
if (SSL_accept(ssl) <= 0) {
tmcd_sslerror();
errno = EAUTH;
return -1;
}
tmcd_sslverify(newsock, 0);
return newsock;
}
......@@ -209,53 +244,136 @@ tmcd_sslaccept(int sock, struct sockaddr *addr, socklen_t *addrlen)
int
tmcd_sslconnect(int sock, const struct sockaddr *name, socklen_t namelen)
{
char *cp = SPEAKSSL;
int cc;
X509 *peer;
char cname[256];
struct hostent *he;
struct in_addr ipaddr;
if (connect(sock, name, namelen) < 0)
return -1;
/*
* Send our special tag which says we speak SSL.
*/
if ((cc = write(sock, cp, strlen(cp))) != strlen(cp)) {
if (cc >= 0) {
error("sslconnect: short write\n");
errno = EIO;
}
return -1;
}
if (! (ssl = SSL_new(ctx))) {
tmcd_sslerror();
errno = EIO;
return -1;
}
if (! SSL_set_fd(ssl, sock)) {
tmcd_sslerror();
errno = EIO;
return -1;
}
if (SSL_connect(ssl) <= 0) {
tmcd_sslerror();
return -1;
goto badauth;
}
/*
* Do the verification dance.
*/
if (SSL_get_verify_result(ssl) != X509_V_OK) {
tmcd_sslprint("Certificate did not verify!\n");
goto badauth;
}
if (! (peer = SSL_get_peer_certificate(ssl))) {
tmcd_sslprint("No certificate was presented by the peer!\n");
goto badauth;
}
/*
* Grab the common name from the cert.
*/
X509_NAME_get_text_by_NID(X509_get_subject_name(peer),
NID_commonName, cname, sizeof(cname));
/*
* On the client, the common name must map to the same
* host we just connected to. This should be enough of
* a check?
*/
ipaddr = ((struct sockaddr_in *)name)->sin_addr;
if (!(he = gethostbyaddr((char *) &ipaddr, sizeof(ipaddr), AF_INET))) {
error("Could not reverse map %s: %s\n",
inet_ntoa(ipaddr), hstrerror(h_errno));
goto badauth;
}
if (strcmp(he->h_name, cname)) {
error("Certificate commonname mismatch: %s!=%s\n",
he->h_name, cname);
goto badauth;
}
tmcd_sslverify(sock, 0);
return 0;
badauth:
errno = EAUTH;
return -1;
}
/*
* Verify the certificate of the peer.
* Verify the certificate of the client.
*/
int
tmcd_sslverify(int sock, char *host)
tmcd_sslverify_client(char *nodeid, char *class, char *type, int islocal)
{
X509 *peer;
char *cp, buf[256];
X509 *peer;
char cname[256], unitname[256];
if (SSL_get_verify_result(ssl) != X509_V_OK) {
tmcd_sslprint("Certificate did not verify!\n");
return 1;
error("sslverify: Certificate did not verify!\n");
return -1;
}
if (! (peer = SSL_get_peer_certificate(ssl))) {
tmcd_sslprint("No certificate was presented by the peer!\n");
return 1;
error("sslverify: No certificate presented!\n");
return -1;
}
if ((cp = X509_NAME_oneline(X509_get_subject_name(peer), 0, 0))) {
printf("Peer subject: %s\n", cp);
free(cp);
/*
* Grab stuff from the cert.
*/
X509_NAME_get_text_by_NID(X509_get_subject_name(peer),
NID_organizationalUnitName,
unitname, sizeof(unitname));
X509_NAME_get_text_by_NID(X509_get_subject_name(peer),
NID_commonName,
cname, sizeof(cname));
/*
* On the server, things are a bit more difficult since
* we share a common cert locally and a per group cert remotely.
*
* Make sure common name matches.
*/
if (strcmp(cname, BOSSNODE)) {
error("sslverify: commonname mismatch: %s!=%s\n",
cname, BOSSNODE);
return -1;
}
if ((cp = X509_NAME_oneline(X509_get_issuer_name(peer), 0, 0))) {
printf("Peer issuer: %s\n", cp);
free(cp);
/*
* If the node is remote, then the unitname must match the type.
* Simply a convention.
*/
if (!islocal && strcmp(unitname, type)) {
error("sslverify: unitname mismatch: %s!=%s\n",
unitname, type);
return -1;
}
return 0;
......@@ -272,8 +390,13 @@ tmcd_sslwrite(int sock, const void *buf, size_t nbytes)
int cc;
errno = 0;
if ((cc = SSL_write(ssl, buf, nbytes)) <= 0) {
if (cc < 0) {
if (isssl || client)
cc = SSL_write(ssl, buf, nbytes);
else
cc = write(sock, buf, nbytes);
if (cc <= 0) {
if (cc < 0 && isssl) {
tmcd_sslerror();
}
return cc;
......@@ -287,11 +410,27 @@ tmcd_sslwrite(int sock, const void *buf, size_t nbytes)
int
tmcd_sslread(int sock, void *buf, size_t nbytes)
{
int cc;
int cc = 0;
if (nosslbuflen) {
char *bp = (char *) buf, *cp = &nosslbuf[nosslbufidx];
while (cc < nbytes && nosslbuflen) {
*bp = *cp;
bp++; cp++; cc++;
nosslbuflen--; nosslbufidx++;
}
return cc;
}
errno = 0;
if ((cc = SSL_read(ssl, buf, nbytes)) <= 0) {
if (cc < 0) {
if (isssl || client)
cc = SSL_read(ssl, buf, nbytes);
else
cc = read(sock, buf, nbytes);
if (cc <= 0) {
if (cc < 0 && isssl) {
tmcd_sslerror();
}
return cc;
......@@ -310,6 +449,7 @@ tmcd_sslclose(int sock)
SSL_free(ssl);
ssl = NULL;
}
nosslbuflen = 0;
close(sock);
return 0;
}
......@@ -345,8 +485,7 @@ tmcd_sslprint(const char *fmt, ...)
if (client) {
fputs(buf, stderr);
fputs("\n", stderr);
}
else
error("%s\n", buf);
error("%s", buf);
}
......@@ -9,7 +9,14 @@ int tmcd_sslconnect(int sock, const struct sockaddr *, socklen_t);
int tmcd_sslwrite(int sock, const void *buf, size_t nbytes);
int tmcd_sslread(int sock, void *buf, size_t nbytes);
int tmcd_sslclose(int sock);
int tmcd_sslverify(int sock, char *host);
int tmcd_sslverify_client(char *, char *, char *, int);
int isssl;
/*
* The client sends this tag to indicate that it is SSL capable.
* Only local nodes can skip SSL. Remote nodes must use SSL!
*/
#define SPEAKSSL "ISPEAKSSL_TMCDV10"
/*
* When compiled to use SSL, redefine the routines appropriately
......
......@@ -2,6 +2,7 @@
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
......@@ -18,6 +19,7 @@
#include "config.h"
#include "ssl.h"
#include "log.h"
#include "tbdefs.h"
#ifdef EVENTSYS
#include "event.h"
......@@ -42,14 +44,15 @@
#define DBNAME_SIZE 64
#define DEFAULT_DBNAME TBDBNAME
static int debug = 0;
int debug = 0;
static int portnum = TBSERVER_PORT;
static char dbname[DBNAME_SIZE];
static struct in_addr myipaddr;
static int nodeidtoexp(char *nodeid, char *pid, char *eid, char *gid);
static int iptonodeid(struct in_addr ipaddr, char *bufp);
static int iptonodeid(struct in_addr, char *, char *, char *, int *);
static int nodeidtonickname(char *nodeid, char *nickname);
static int nodeidtocontrolnet(char *nodeid, int *net);
static int checkdbredirect(struct in_addr ipaddr);
static int checkdbredirect(char *nodeid);
static void tcpserver(int sock);
static void udpserver(int sock);
static int handle_request(int, struct sockaddr_in *, char *, int);
......@@ -76,7 +79,7 @@ static event_handle_t event_handle = NULL;
*/
#define COMMAND_PROTOTYPE(x) \
static int \
x(int sock, struct in_addr ipaddr, char *rdata, int tcp, int vers)
x(int sock, char *nodeid, char *rdata, int tcp, int vers)
COMMAND_PROTOTYPE(doreboot);
COMMAND_PROTOTYPE(dostatus);
......@@ -104,7 +107,7 @@ COMMAND_PROTOTYPE(docreator);
struct command {
char *cmdname;
int (*func)(int, struct in_addr, char *, int, int);
int (*func)(int, char *, char *, int, int);
} command_array[] = {
{ "reboot", doreboot },
{ "status", dostatus },
......@@ -161,8 +164,8 @@ static void
cleanup()
{
signal(SIGHUP, SIG_IGN);
killpg(0, SIGHUP);
killme = 1;
killpg(0, SIGHUP);
}
int
......@@ -173,6 +176,7 @@ main(int argc, char **argv)
struct sockaddr_in name;
FILE *fp;
char buf[BUFSIZ];
struct hostent *he;
extern char build_info[];
while ((ch = getopt(argc, argv, "dp:c:")) != -1)
......@@ -215,6 +219,21 @@ main(int argc, char **argv)
info("daemon starting (version %d)\n", CURRENT_VERSION);
info("%s\n", build_info);
/*
* Grab our IP for security check below.
*/
#ifdef LBS
strcpy(buf, BOSSNODE);
#else
if (gethostname(buf, sizeof(buf)) < 0)
pfatal("getting hostname");
#endif
if ((he = gethostbyname(buf)) == NULL) {
error("Could not get IP (%s) - %s\n", buf, hstrerror(h_errno));
exit(1);
}
memcpy((char *)&myipaddr, he->h_addr, he->h_length);
/*
* Setup TCP socket for incoming connections.
*/
......@@ -343,7 +362,7 @@ main(int argc, char **argv)
}
/*
* Listen for UDP requests. This not a secure channel, and so this should
* Listen for UDP requests. This is not a secure channel, and so this should
* eventually be killed off.
*/
static void
......@@ -375,8 +394,7 @@ udpserver(int sock)
}
/*
* Listen for TCP requests. This not a secure channel, and so this should
* eventually be killed off.
* Listen for TCP requests.
*/
static void
tcpserver(int sock)
......@@ -421,7 +439,10 @@ handle_request(int sock, struct sockaddr_in *client, char *rdata, int istcp)
struct sockaddr_in redirect_client;
int redirect = 0;
char buf[BUFSIZ], *bp;
int i, cc, err = 0;
char nodeid[TBDB_FLEN_NODEID];
char class[TBDB_FLEN_NODECLASS];
char type[TBDB_FLEN_NODETYPE];
int i, cc, islocal, err = 0;
int version = DEFAULT_VERSION;
cc = strlen(rdata);
......@@ -459,14 +480,24 @@ handle_request(int sock, struct sockaddr_in *client, char *rdata, int istcp)
bp++;
}
#ifndef TESTMODE
/* Start with default DB */
strcpy(dbname, DEFAULT_DBNAME);
/*
* IN TESTMODE, we allow redirect.
* Otherwise not since that would be a (minor) privacy
* risk, by allowing testbed nodes to get info for other
* nodes.
* Map the ip to a nodeid.
*/
if (redirect) {
if (iptonodeid(client->sin_addr, nodeid, class, type, &islocal)) {
error("No such node: %s\n", inet_ntoa(client->sin_addr));
goto skipit;
}
/*
* Redirect is allowed from the local host only!
* I use this for testing. See below where I test redirect
* if the verification fails.
*/
if (redirect &&
redirect_client.sin_addr.s_addr != myipaddr.s_addr) {
char buf1[32], buf2[32];
strcpy(buf1, inet_ntoa(redirect_client.sin_addr));
......@@ -475,21 +506,49 @@ handle_request(int sock, struct sockaddr_in *client, char *rdata, int istcp)
info("%s INVALID REDIRECT: %s\n", buf1, buf2);
goto skipit;
}
#ifdef WITHSSL
/*
* If the connection is not SSL, then it must be a local node.
*/
if (isssl) {
if (tmcd_sslverify_client(nodeid, class, type, islocal)) {
error("%s: SSL verification failure\n", nodeid);
if (! redirect)
goto skipit;
}
}
else if (!islocal) {
error("%s: Remote node connected without SSL!\n", nodeid);
/*
* Allow for now, until ron nodes updated.
* if (! redirect)
* goto skipit;
*/
}
#else
/*
* When not compiled for ssl, do not allow remote connections.
*/
if (!islocal) {
error("%s: Remote node without SSL!\n", nodeid);
/*
* Allow for now, until ron nodes updated.
* if (! redirect)
* goto skipit;
*/
}
#endif
/*
* Check for a redirect using the default DB. This allows
* for a simple redirect to a secondary DB for testing.
* Not very general. Might change to full blown tmcd
* redirection at some point, but this is a very quick and
* easy hack. Upon return, the dbname has been changed if
* redirection is in force.
* Upon return, the dbname has been changed if redirected.
*/
strcpy(dbname, DEFAULT_DBNAME);
if (checkdbredirect(client->sin_addr)) {
if (checkdbredirect(nodeid)) {
/* Something went wrong */
goto skipit;
}
/*
* Figure out what command was given.
*/
......@@ -498,36 +557,31 @@ handle_request(int sock, struct sockaddr_in *client, char *rdata, int istcp)
strlen(command_array[i].cmdname)) == 0)
break;
/*
* And execute it.
*/
if (i == numcommands) {
info("%s INVALID REQUEST: %.8s\n",
inet_ntoa(client->sin_addr), bp);
info("%s: INVALID REQUEST: %.8s\n", nodeid, bp);
goto skipit;
}
else {
bp += strlen(command_array[i].cmdname);
/*
* XXX hack, don't log "log" contents,
* both for privacy and to keep our syslog smaller.
*/
if (command_array[i].func == dolog)
info("%s log %d chars\n",
inet_ntoa(client->sin_addr), strlen(bp));
else
info("%s vers:%d %s\n",
inet_ntoa(client->sin_addr),
version, command_array[i].cmdname);
/*
* Execute it.
*/
bp += strlen(command_array[i].cmdname);
err = command_array[i].func(sock, client->sin_addr,
bp, istcp, version);
/*
* XXX hack, don't log "log" contents,
* both for privacy and to keep our syslog smaller.
*/
if (command_array[i].func == dolog)
info("%s: log %d chars\n", nodeid, strlen(bp));
else
info("%s: vers:%d %s\n", nodeid,
version, command_array[i].cmdname);
info("%s %s: returned %d\n",
inet_ntoa(client->sin_addr),
command_array[i].cmdname, err);
}
err = command_array[i].func(sock, nodeid, bp, istcp, version);
if (err)
info("%s: %s: returned %d\n",
nodeid, command_array[i].cmdname, err);
skipit:
if (!istcp)
......@@ -542,18 +596,10 @@ handle_request(int sock, struct sockaddr_in *client, char *rdata, int istcp)
COMMAND_PROTOTYPE(doreboot)
{
MYSQL_RES *res;
char nodeid[32];
char pid[64];
char eid[64];
char gid[64];
if (iptonodeid(ipaddr, nodeid)) {
error("REBOOT: %s: No such node\n", inet_ntoa(ipaddr));
return 1;
}
info("REBOOT: %s is reporting a reboot\n", nodeid);
/*
* Clear the current_reloads for this node, in case it just finished
* reloading. This needs to happen regardless of whether or not the
......@@ -596,7 +642,8 @@ COMMAND_PROTOTYPE(doreboot)
* See if the node was in the reload state. If so we need to clear it
* and its reserved status.
*/
res = mydb_query("select node_id from scheduled_reloads where node_id='%s'",
res = mydb_query("select node_id from scheduled_reloads "
"where node_id='%s'",
1, nodeid);
if (!res) {
error("REBOOT: %s: DB Error getting reload!\n", nodeid);
......@@ -608,7 +655,8 @@ COMMAND_PROTOTYPE(doreboot)
}
mysql_free_result(res);
if (mydb_update("delete from scheduled_reloads where node_id='%s'", nodeid)) {
if (mydb_update("delete from scheduled_reloads where node_id='%s'",
nodeid)) {
error("REBOOT: %s: DB Error clearing reload!\n", nodeid);
return 1;
}
......@@ -628,18 +676,12 @@ COMMAND_PROTOTYPE(doreboot)
*/
COMMAND_PROTOTYPE(dostatus)
{
char nodeid[32];
char pid[64];
char eid[64];
char gid[64];
char nickname[128];
char buf[MYBUFSIZE];
if (iptonodeid(ipaddr, nodeid)) {
error("STATUS: %s: No such node\n", inet_ntoa(ipaddr));
return 1;
}
/*
* Now check reserved table
*/
......@@ -670,18 +712,12 @@ COMMAND_PROTOTYPE(doifconfig)
{
MYSQL_RES *res;
MYSQL_ROW row;
char nodeid[32];
char pid[64];
char eid[64];