Commit 01234f97 authored by Leigh B. Stoller's avatar Leigh B. Stoller
Browse files

A doosy! I added two new modes of operation in support of jails. Only

for BSD of course. First is a "proxy" mode that is used outside of a
jail, to forward tmcc requests from inside the jail to boss over the
normal ssl channel (when a remote node). We remove the pem files from
inside the jail so it has no way to form a secure connection to tmcd
on its own, and tmcd rejects non-ssl connections from remote nodes (it
should probably reject them from local jails too). Second change is a
"unix socket" mode that is the compliment to the proxy; tmcc inside of
a jail connects to the tmcc proxy outside the jail via a unix domain
socket that can be shared between the two because the outer
environment can see inside the jailed filesystems (the jail sees a
chroot environment). When the jail is started, the initial root shell
gets an environment variable called TMCCUNIXPATH which holds the path
to the socket. This makes it easy for anything started from that shell
of course, but its still a minor pain when invoking tmcc from
elsehwere, but that does not really happen, except when running it by
hand. Anyway, tmcc forms a unix socket to the proxy and does its
thing. The proxy filters out VNODE= and PRIVKEY= arguments, and
inserts its own into the command string.  This prevents a jail from
trying to impersonate another vnode.
parent 678a5a34
......@@ -3,10 +3,11 @@
* Copyright (c) 2000-2002 University of Utah and the Flux Group.
* All rights reserved.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <sys/fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <syslog.h>
......@@ -27,17 +28,22 @@
#ifndef STANDALONE
#include "config.h"
#endif
#undef BOSSNODE
#ifndef BOSSNODE
#include <resolv.h>
#endif
#ifndef KEYFILE
#define KEYFILE "/etc/emulab.pkey"
#endif
#define UNIXSOCKVAR "TMCCUNIXPATH"
/*
* We search a couple of dirs for the bossnode file.
*/
static char *bossnodedirs[] = {
"/etc/testbed",
"/etc/emulab",
"/etc/rc.d/testbed",
"/usr/local/etc/testbed",
"/usr/local/etc/emulab",
......@@ -51,23 +57,31 @@ static int portlist[] = {
TBSERVER_PORT,
TBSERVER_PORT2,
};
static int numports = sizeof(portlist)/sizeof(int);
void sigcatcher(int foo);
char *getbossnode(void);
#ifdef UDP
void doudp(int, char **, int, struct in_addr, int, char *, int);
#endif
/* Locals */
static int numports = sizeof(portlist)/sizeof(int);
static int debug = 0;
static char *logfile = NULL;
/* Forward decls */
static char *getbossnode(void);
static int doudp(char *, int, struct in_addr, int);
static int dotcp(char *, int, struct in_addr);
static int dounix(char *, int, char *);
static void beproxy(char *, struct in_addr, char *);
static int dooutput(int, char *, int);
char *usagestr =
"usage: tmcc [-u] [-v versnum] [-p #] [-s server] <command>\n"
"usage: tmcc [options] <command>\n"
" -d Turn on debugging\n"
" -s server Specify a tmcd server to connect to\n"
" -p portnum Specify a port number to connect to\n"
" -v versnum Specify a version number for tmcd\n"
" -n vnodeid Specify the vnodeid\n"
" -k keyfile Specify the private keyfile\n"
" -u Use UDP instead of TCP\n"
" -l path Use named unix domain socket instead of TCP\n"
" -t timeout Timeout waiting for the controller.\n"
" -x path Be a tmcc proxy, using the named unix domain socket\n"
"\n";
void
......@@ -81,24 +95,26 @@ usage()
int
main(int argc, char **argv)
{
int sock, data, n, cc, ch;
struct sockaddr_in name;
int n, ch;
struct hostent *he;
struct in_addr serverip;
char buf[MYBUFSIZE], *bp, *response = "";
char *bossnode = NULL;
int version = CURRENT_VERSION;
char *vnodeid = NULL;
char *keyfile = NULL;
char *privkey = NULL;
int waitfor = 0;
char buf[MYBUFSIZE], *bp;
FILE *fp;
#ifdef UDP
int useudp = 0;
#endif
while ((ch = getopt(argc, argv, "v:s:p:un:t:k:")) != -1)
int useudp = 0;
char *unixpath = NULL;
char *bossnode = NULL;
int version = CURRENT_VERSION;
char *vnodeid = NULL;
char *keyfile = NULL;
char *privkey = NULL;
char *proxypath= NULL;
int waitfor = 0;
while ((ch = getopt(argc, argv, "v:s:p:un:t:k:x:l:do:")) != -1)
switch(ch) {
case 'd':
debug++;
break;
case 'p':
portlist[0] = atoi(optarg);
numports = 1;
......@@ -118,53 +134,47 @@ main(int argc, char **argv)
case 't':
waitfor = atoi(optarg);
break;
#ifdef UDP
case 'u':
useudp = 1;
break;
#endif
case 'x':
proxypath = optarg;
break;
case 'l':
unixpath = optarg;
break;
case 'o':
logfile = optarg;
break;
default:
usage();
}
argv += optind;
argc -= optind;
if (argc < 1 || argc > 5) {
if (!proxypath && (argc < 1 || argc > 5)) {
usage();
}
argv += optind;
if (unixpath && proxypath)
usage();
/*
* How do we find our bossnode?
*
* 1. Command line.
* 2. From a file in the list above.
* 3. nameserver goo below.
* Allow for environment to specify a unix socket, which is a
* connection to a tmcc proxy. Note that a specific -l option
* overrides the environment variable.
*/
if (!bossnode) {
/*
* Search for the file.
*/
char **cp = bossnodedirs;
while (*cp) {
sprintf(buf, "%s/%s", *cp, BOSSNODE_FILENAME);
if (!unixpath && !proxypath && (bp = getenv(UNIXSOCKVAR)))
unixpath = bp;
if (access(buf, R_OK) == 0)
break;
cp++;
}
if (*cp) {
if ((fp = fopen(buf, "r")) != NULL) {
if (fgets(buf, sizeof(buf), fp)) {
if ((bp = strchr(buf, '\n')))
*bp = (char) NULL;
bossnode = strdup(buf);
}
fclose(fp);
}
}
if (unixpath && (keyfile || bossnode)) {
fprintf(stderr,
"You may not use the -k or -s with the -l option\n");
usage();
}
if (!bossnode)
bossnode = getbossnode();
he = gethostbyname(bossnode);
if (he)
memcpy((char *)&serverip, he->h_addr, he->h_length);
......@@ -173,6 +183,14 @@ main(int argc, char **argv)
exit(1);
}
/*
* Handle built-in "bossinfo" command
*/
if (!proxypath && (strcmp(argv[0], "bossinfo") == 0)) {
printf("%s %s\n", bossnode, inet_ntoa(serverip));
exit(0);
}
/*
* Grab the key. Its not an error if we do not find it.
*/
......@@ -195,32 +213,134 @@ main(int argc, char **argv)
fclose(fp);
}
if (waitfor) {
alarm(waitfor);
/*
* Build up the command request string.
*/
buf[0] = '\0';
/* Tack on vnodeid */
if (vnodeid) {
sprintf(&buf[strlen(buf)], "VNODEID=%s ", vnodeid);
}
/* Tack on privkey */
if (privkey) {
sprintf(&buf[strlen(buf)], "PRIVKEY=%s ", privkey);
}
/*
* Handle built-in "bossinfo" command
* If specified udp with unixpath, then pass through a UDP flag
* to the proxy, which will do what it thinks is appropriate.
*/
if (strcmp(argv[0], "bossinfo") == 0) {
printf("%s %s\n", bossnode, inet_ntoa(serverip));
if (useudp && unixpath) {
sprintf(&buf[strlen(buf)], "USEUDP=1 ");
useudp = 0;
}
/*
* In proxy mode ...
*/
if (proxypath) {
beproxy(proxypath, serverip, buf);
exit(0);
}
#ifdef UDP
if (useudp) {
doudp(argc, argv, version, serverip,
portlist[0], vnodeid, waitfor);
/*
* Never returns.
*/
abort();
/* Tack on version number */
sprintf(&buf[strlen(buf)], "VERSION=%d ", version);
/*
* Since we've gone through a getopt() pass, argv[0] is now the
* first argument
*/
n = strlen(buf);
while (argc && n < sizeof(buf)) {
n += snprintf(&buf[n], sizeof(buf) - (n + 1), "%s ", argv[0]);
argc--;
argv++;
}
if (n >= sizeof(buf)) {
fprintf(stderr, "Command too large!\n");
exit(-1);
}
buf[n] = '\0';
/*
* When a timeout is requested, just let the signal kill us.
*/
if (waitfor) {
alarm(waitfor);
}
if (useudp)
n = doudp(buf, fileno(stdout), serverip, portlist[0]);
else if (unixpath)
n = dounix(buf, fileno(stdout), unixpath);
else
n = dotcp(buf, fileno(stdout), serverip);
exit(n);
}
/*
* Find the bossnode name if one was not specified on the command line.
*/
static char *
getbossnode(void)
{
#ifdef BOSSNODE
return strdup(BOSSNODE);
#else
struct hostent *he;
FILE *fp;
char buf[BUFSIZ], **cp = bossnodedirs, *bp;
/*
* Search for the file.
*/
while (*cp) {
sprintf(buf, "%s/%s", *cp, BOSSNODE_FILENAME);
if (access(buf, R_OK) == 0)
break;
cp++;
}
if (*cp) {
if ((fp = fopen(buf, "r")) != NULL) {
if (fgets(buf, sizeof(buf), fp)) {
if ((bp = strchr(buf, '\n')))
*bp = (char) NULL;
fclose(fp);
return(strdup(buf));
}
fclose(fp);
}
}
/*
* Nameserver goo
*/
res_init();
he = gethostbyaddr((char *)&_res.nsaddr.sin_addr,
sizeof(struct in_addr), AF_INET);
if (he && he->h_name)
return strdup(he->h_name);
return("UNKNOWN");
#endif
}
/*
* TCP version, which uses ssl if compiled in.
*/
static int
dotcp(char *data, int outfd, struct in_addr serverip)
{
int n, sock, cc;
struct sockaddr_in name;
char *bp, buf[MYBUFSIZE];
#ifdef WITHSSL
if (tmcd_client_sslinit()) {
printf("SSL initialization failed!\n");
exit(1);
return -1;
}
#endif
while (1) {
......@@ -229,7 +349,7 @@ main(int argc, char **argv)
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("creating stream socket:");
exit(1);
return -1;
}
/* Create name. */
......@@ -244,7 +364,7 @@ main(int argc, char **argv)
if (errno != ECONNREFUSED) {
perror("connecting stream socket");
CLOSE(sock);
exit(1);
return -1;
}
CLOSE(sock);
}
......@@ -254,45 +374,17 @@ main(int argc, char **argv)
}
foundit:
data = 1;
if (setsockopt(sock, SOL_SOCKET,
SO_KEEPALIVE, &data, sizeof(data)) < 0) {
n = 1;
if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &n, sizeof(n)) < 0) {
perror("setsockopt SO_KEEPALIVE");
goto bad;
}
/* Start with version number */
sprintf(buf, "VERSION=%d ", version);
/* Tack on vnodeid */
if (vnodeid) {
sprintf(&buf[strlen(buf)], "VNODEID=%s ", vnodeid);
}
/* Tack on privkey */
if (privkey) {
sprintf(&buf[strlen(buf)], "PRIVKEY=%s ", privkey);
}
/*
* Since we've gone through a getopt() pass, argv[0] is now the
* first argument
* Write the command to the socket and wait for the response.
*/
n = strlen(buf);
while (argc && n < sizeof(buf)) {
n += snprintf(&buf[n], sizeof(buf) - n, "%s ", argv[0]);
argc--;
argv++;
}
if (n >= sizeof(buf)) {
fprintf(stderr, "Command too large!\n");
goto bad;
}
/*
* Write the command to the socket and wait for the response
*/
bp = buf;
bp = data;
n = strlen(data);
while (n) {
if ((cc = WRITE(sock, bp, n)) <= 0) {
if (cc < 0) {
......@@ -307,132 +399,296 @@ main(int argc, char **argv)
}
while (1) {
if ((cc = READ(sock, buf, sizeof(buf))) <= 0) {
if ((cc = READ(sock, buf, sizeof(buf) - 1)) <= 0) {
if (cc < 0) {
perror("Reading from socket:");
goto bad;
}
break;
}
buf[cc] = '\0';
bp = (char *) malloc(strlen(response) + cc + 1);
assert(bp);
strcpy(bp, response);
strcat(bp, buf);
response = bp;
if (dooutput(outfd, buf, cc) < 0)
goto bad;
}
printf("%s", response);
CLOSE(sock);
exit(0);
return 0;
bad:
CLOSE(sock);
exit(1);
}
void
sigcatcher(int foo)
{
return -1;
}
#ifndef BOSSNODE
#include <resolv.h>
#endif
char *
getbossnode(void)
{
#ifdef BOSSNODE
return strdup(BOSSNODE);
#else
struct hostent *he;
res_init();
he = gethostbyaddr((char *)&_res.nsaddr.sin_addr,
sizeof(struct in_addr), AF_INET);
if (he && he->h_name)
return strdup(he->h_name);
return("UNKNOWN");
#endif
}
#ifdef UDP
/*
* Not very robust, send a single request, read a single reply.
*/
void
doudp(int argc, char **argv,
int vers, struct in_addr serverip, int portnum, char *vnodeid,
int waitfor)
static int
doudp(char *data, int outfd, struct in_addr serverip, int portnum)
{
int sock, length, n, cc;
struct sockaddr_in name, client;
char buf[MYBUFSIZE], *bp, *response = "";
char buf[MYBUFSIZE];
/* Create socket from which to read. */
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
perror("creating dgram socket:");
exit(1);
return -1;
}
/* Create name. */
name.sin_family = AF_INET;
name.sin_addr = serverip;
name.sin_port = htons(portnum);
/* Start with version number */
sprintf(buf, "VERSION=%d ", vers);
/* Tack on vnodeid */
if (vnodeid) {
sprintf(&buf[strlen(buf)], "VNODEID=%s ", vnodeid);
}
/*
* Since we've gone through a getopt() pass, argv[0] is now the
* first argument
*/
n = strlen(buf);
while (argc && n < sizeof(buf)) {
n += snprintf(&buf[n], sizeof(buf) - n, "%s ", argv[0]);
argc--;
argv++;
}
if (n >= sizeof(buf)) {
fprintf(stderr, "Command too large!\n");
exit(1);
}
name.sin_port = htons(portnum);
/*
* Write the command to the socket and wait for the response
*/
cc = sendto(sock, buf, n, 0, (struct sockaddr *)&name, sizeof(name));
n = strlen(data);
cc = sendto(sock, data, n, 0, (struct sockaddr *)&name, sizeof(name));
if (cc != n) {
if (cc < 0) {
perror("Writing to socket:");
exit(1);
return -1;
}
fprintf(stderr, "short write (%d != %d)\n", cc, n);
exit(1);
return -1;
}
cc = recvfrom(sock, buf, sizeof(buf), 0,
cc = recvfrom(sock, buf, sizeof(buf) - 1, 0,
(struct sockaddr *)&client, &length);
if (cc < 0) {
perror("Reading from socket:");
exit(1);
return -1;
}
buf[cc] = '\0';
bp = (char *) malloc(strlen(response) + cc + 1);
assert(bp);
strcpy(bp, response);
strcat(bp, buf);
response = bp;
printf("%s", response);
close(sock);
if (dooutput(outfd, buf, cc) < 0)
return -1;
return 0;
}
/*
* Unix domain version.
*/
static int
dounix(char *data, int outfd, char *unixpath)
{
#ifdef linux
fprintf(stderr, "unix domain socket mode not supported on linux!\n");
return -1;
#else
int n, sock, cc, length;
struct sockaddr_un sunaddr;
char *bp, buf[MYBUFSIZE];
sunaddr.sun_family = AF_UNIX;
strlcpy(sunaddr.sun_path, unixpath, sizeof(sunaddr.sun_path));
sunaddr.sun_len = length = SUN_LEN(&sunaddr)+1;
/* Create socket from which to read. */
sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock < 0) {
perror("creating stream socket:");
return -1;
}
if (connect(sock, (struct sockaddr *) &sunaddr, length) < 0) {
perror("connecting unix domain socket");
close(sock);
return -1;
}
/*
* Write the command to the socket and wait for the response.
*/
bp = data;
n = strlen(data);
while (n) {
if ((cc = write(sock, bp, n)) <= 0) {
if (cc < 0) {
perror("Writing to socket:");
goto bad;
}
fprintf(stderr, "write aborted");
goto bad;
}
bp += cc;
n -= cc;
}
while (1) {
if ((cc = read(sock, buf, sizeof(buf) - 1)) <= 0) {
if (cc < 0) {
perror("Reading from socket:");
goto bad;
}
break;
}
if (dooutput(outfd, buf, cc) < 0)
goto bad;
}