Commit 55b431cf authored by Leigh B. Stoller's avatar Leigh B. Stoller
Browse files

Remove code that required tmcd to run as root, and add the necessary

code to run as uid/gid nobody/nobody. I had to remove dolog(), which
was to support the oskit on the dnards, so no one will miss that!
I also removed the tar/rpm download support since we now do that via
the web interface. To support sfshostid, I had to chown/chgrp
/proj/.sfs to the nobody/nobody so that tmcd willbe able to create the
symlinks in that directory.
parent 3328323a
......@@ -55,6 +55,8 @@
#define RELOADEID "reloading"
#define FSHOSTID "/usr/testbed/etc/fshostid"
#define DOTSFS ".sfs"
#define RUNASUSER "nobody"
#define RUNASGROUP "nobody"
#define TESTMODE
#define DEFAULTNETMASK "255.255.255.0"
......@@ -187,7 +189,6 @@ COMMAND_PROTOTYPE(dostartcmd);
COMMAND_PROTOTYPE(dostartstat);
COMMAND_PROTOTYPE(doready);
COMMAND_PROTOTYPE(doreadycount);
COMMAND_PROTOTYPE(dolog);
COMMAND_PROTOTYPE(domounts);
COMMAND_PROTOTYPE(dosfshostid);
COMMAND_PROTOTYPE(doloadinfo);
......@@ -202,8 +203,6 @@ COMMAND_PROTOTYPE(dovnodelist);
COMMAND_PROTOTYPE(dosubnodelist);
COMMAND_PROTOTYPE(doisalive);
COMMAND_PROTOTYPE(doipodinfo);
COMMAND_PROTOTYPE(doatarball);
COMMAND_PROTOTYPE(doanrpm);
COMMAND_PROTOTYPE(dontpinfo);
COMMAND_PROTOTYPE(dontpdrift);
COMMAND_PROTOTYPE(dojailconfig);
......@@ -252,7 +251,6 @@ struct command {
{ "startstat", FULLCONFIG_NONE, dostartstat },
{ "readycount", FULLCONFIG_NONE, doreadycount },
{ "ready", FULLCONFIG_NONE, doready },
{ "log", FULLCONFIG_NONE, dolog },
{ "mounts", FULLCONFIG_ALL, domounts },
{ "sfshostid", FULLCONFIG_NONE, dosfshostid },
{ "loadinfo", FULLCONFIG_NONE, doloadinfo},
......@@ -269,8 +267,6 @@ struct command {
{ "ipodinfo", FULLCONFIG_NONE, doipodinfo},
{ "ntpinfo", FULLCONFIG_PHYS, dontpinfo},
{ "ntpdrift", FULLCONFIG_NONE, dontpdrift},
{ "tarball", FULLCONFIG_NONE, doatarball},
{ "rpm", FULLCONFIG_NONE, doanrpm},
{ "jailconfig", FULLCONFIG_VIRT, dojailconfig},
{ "plabconfig", FULLCONFIG_VIRT, doplabconfig},
{ "subconfig", FULLCONFIG_NONE, dosubconfig},
......@@ -455,6 +451,39 @@ main(int argc, char **argv)
(void) fclose(fp);
}
/*
* Change to non-root user!
*/
if (geteuid() == 0) {
struct passwd *pw;
uid_t uid;
gid_t gid;
/*
* Must be a valid user of course.
*/
if ((pw = getpwnam(RUNASUSER)) == NULL) {
error("invalid user: %s", RUNASUSER);
exit(1);
}
uid = pw->pw_uid;
gid = pw->pw_gid;
if (setgroups(1, &gid)) {
errorc("setgroups");
exit(1);
}
if (setgid(gid)) {
errorc("setgid");
exit(1);
}
if (setuid(uid)) {
errorc("setuid");
exit(1);
}
info("Flipped to user/group %d/%d\n", uid, gid);
}
/*
* Now fork a set of children to handle requests. We keep the
* pool at a set level. No need to get too fancy at this point,
......@@ -917,14 +946,7 @@ handle_request(int sock, struct sockaddr_in *client, char *rdata, int istcp)
#else
cp = (istcp ? "TCP" : "UDP");
#endif
/*
* XXX hack, don't log "log" contents,
* both for privacy and to keep our syslog smaller.
*/
if (command_array[i].func == dolog)
info("%s: vers:%d %s log %d chars\n",
reqp->nodeid, version, cp, strlen(rdata));
else if (command_array[i].func != doisalive || verbose)
if (command_array[i].func != doisalive || verbose)
info("%s: vers:%d %s %s\n", reqp->nodeid,
version, cp, command_array[i].cmdname);
......@@ -1125,7 +1147,6 @@ COMMAND_PROTOTYPE(doifconfig)
mysql_free_result(res);
/* Veth interfaces are new. */
doveths:
if (vers < 10)
return 0;
......@@ -2601,48 +2622,6 @@ COMMAND_PROTOTYPE(doreadycount)
return 0;
}
static char logfmt[] = "/proj/%s/logs/%s.log";
/*
* Log some text to a file in the /proj/<pid>/exp/<eid> directory.
*/
COMMAND_PROTOTYPE(dolog)
{
char logfile[TBDB_FLEN_PID+TBDB_FLEN_EID+sizeof(logfmt)];
FILE *fd;
time_t curtime;
char *tstr;
/*
* Find the pid/eid of the requesting node
*/
if (!reqp->allocated) {
if (verbose)
info("LOG: %s: Node is free\n", reqp->nodeid);
return 1;
}
snprintf(logfile, sizeof(logfile)-1, logfmt, reqp->pid, reqp->eid);
fd = fopen(logfile, "a");
if (fd == NULL) {
error("LOG: %s: Could not open %s\n", reqp->nodeid, logfile);
return 1;
}
curtime = time(0);
tstr = ctime(&curtime);
tstr[19] = 0; /* no year */
tstr += 4; /* or day of week */
while (isspace(*rdata))
rdata++;
fprintf(fd, "%s: %s\n\n%s\n=======\n", tstr, reqp->nodeid, rdata);
fclose(fd);
return 0;
}
/*
* Return mount stuff.
*/
......@@ -2985,7 +2964,11 @@ COMMAND_PROTOTYPE(dosfshostid)
OUTPUT(sfspath, sizeof(sfspath), "/sfs/%s", nodehostid);
OUTPUT(dspath, sizeof(dspath), "/proj/%s/%s.%s.%s", DOTSFS,
reqp->nickname, reqp->eid, reqp->pid);
/*
* Create the symlink. The directory in which this is done has to be
* either owned by the same uid used to run tmcd, or in the same group.
*/
if (safesymlink(sfspath, dspath) < 0) {
return 1;
}
......@@ -4260,377 +4243,6 @@ COMMAND_PROTOTYPE(dontpdrift)
return 0;
}
static int sendafile(int sock, tmcdreq_t *reqp, int tcp, char *filename,
char *filetype, char *cmdname);
/*
* Return a tarball.
* The tarball being requested has to be in the tarballs list for the
* node of course. Has to be a tcp connection of course, and all remote
* tcp connections are required to be ssl'ized.
*/
COMMAND_PROTOTYPE(doatarball)
{
MYSQL_RES *res;
MYSQL_ROW row;
char tarname[1024+1];
int okay = 0;
char *bp, *sp, *tp;
/*
* Check reserved table
*/
if (!reqp->allocated) {
error("GETTAR: %s: Node is free\n", reqp->nodeid);
return 1;
}
/*
* Pick up the name from the argument. Limit to usual MAXPATHLEN.
*/
if (sscanf(rdata, "%1024s", tarname) != 1) {
error("GETTAR: %s: Bad arguments\n", reqp->nodeid);
return 1;
}
/*
* Get the tarball list from the DB. The requested path must be
* on the list of tarballs for this node.
*/
res = mydb_query("select tarballs from nodes where node_id='%s' ",
1, reqp->nodeid);
if (!res) {
error("GETTAR: %s: DB Error getting tarballs!\n",
reqp->nodeid);
return 1;
}
if ((int)mysql_num_rows(res) == 0) {
mysql_free_result(res);
error("GETTAR: %s: Invalid Tarball: %s!\n",
reqp->nodeid, tarname);
return 1;
}
/*
* Text string is a colon separated list of "dir filename".
*/
row = mysql_fetch_row(res);
if (! row[0] || !row[0][0]) {
mysql_free_result(res);
error("GETTAR: %s: Invalid Tarball: %s!\n",
reqp->nodeid, tarname);
return 1;
}
bp = row[0];
sp = bp;
do {
bp = strsep(&sp, ":");
if ((tp = strchr(bp, ' ')) == NULL)
continue;
*tp++ = '\0';
if (strcmp(tp, tarname) == 0) {
okay = 1;
break;
}
} while ((bp = sp));
mysql_free_result(res);
if (!okay) {
error("GETTAR: %s: Invalid Tarball: %s!\n",
reqp->nodeid, tarname);
return 1;
}
return sendafile(sock, reqp, tcp, tarname, "tarfile", "GETTAR");
}
/*
* Return an RPM file.
* The rpm being requested has to be in the rpms list for the
* node of course. Has to be a tcp connection of course, and all remote
* tcp connections are required to be ssl'ized.
*/
COMMAND_PROTOTYPE(doanrpm)
{
MYSQL_RES *res;
MYSQL_ROW row;
char rpmname[1024+1];
int okay = 0;
char *bp, *sp;
/*
* Check reserved table
*/
if (!reqp->allocated) {
error("GETRPM: %s: Node is free\n", reqp->nodeid);
return 1;
}
/*
* Pick up the name from the argument. Limit to usual MAXPATHLEN.
*/
if (sscanf(rdata, "%1024s", rpmname) != 1) {
error("GETRPM: %s: Bad arguments\n", reqp->nodeid);
return 1;
}
/*
* Get the rpm list from the DB. The requested path must be
* on the list of rpms for this node.
*/
res = mydb_query("select rpms from nodes where node_id='%s' ",
1, reqp->nodeid);
if (!res) {
error("GETRPM: %s: DB Error getting rpms!\n",
reqp->nodeid);
return 1;
}
if ((int)mysql_num_rows(res) == 0) {
mysql_free_result(res);
error("GETRPM: %s: Invalid RPM: %s!\n",
reqp->nodeid, rpmname);
return 1;
}
/*
* Text string is a colon separated list of filenames.
*/
row = mysql_fetch_row(res);
if (! row[0] || !row[0][0]) {
mysql_free_result(res);
error("GETRPM: %s: Invalid RPM: %s!\n",
reqp->nodeid, rpmname);
return 1;
}
bp = row[0];
sp = bp;
do {
bp = strsep(&sp, ":");
if (strcmp(bp, rpmname) == 0) {
okay = 1;
break;
}
} while ((bp = sp));
mysql_free_result(res);
if (!okay) {
error("GETRPM: %s: Invalid RPM: %s!\n",
reqp->nodeid, rpmname);
return 1;
}
return sendafile(sock, reqp, tcp, rpmname, "rpm", "GETRPM");
}
static int safesyscall(int sysnum, ...);
/*
* Return a tar or RPM file. Verifies that the user has access to the file
* in question. If so, the file is sent back on the connection, prefixed by
* the file's size. It is up to the caller to ensure it is a registered tar
* or RPM file.
*/
static int
sendafile(int sock, tmcdreq_t *reqp, int tcp, char *filename,
char *filetype, char *cmdname)
{
char buf[1024 * 32];
int cc, fd;
char *bp;
struct stat statbuf;
struct group *grp;
struct passwd *pwd;
/*
* Ensure that we do not get tricked into returning a file outside
* of /proj, /users, or /groups. We could put a check in the frontend
* where tarfiles/rpms is set, but this is much safer given the
* potential for disaster.
*
* XXX I know, realpath is not really a syscall.
*/
if (safesyscall(696969, filename, buf) == NULL) {
errorc("%s: %s: realpath failure %s!",
cmdname, reqp->nodeid, filename);
return 1;
}
if ((bp = strchr(&buf[1], '/')) == NULL) {
errorc("%s: %s: could not parse %s!",
cmdname, reqp->nodeid, buf);
return 1;
}
*bp = NULL;
if (strcmp(buf, PROJDIR) &&
strcmp(buf, GROUPDIR) &&
strcmp(buf, USERDIR)) {
*bp = '/';
error("%s: %s: illegal path: %s --> %s!\n",
cmdname, reqp->nodeid, filename, buf);
return 1;
}
*bp = '/';
/*
* Better be readable!
*/
if ((fd = safesyscall(SYS_open, filename, O_RDONLY)) < 0) {
errorc("%s: %s: Could not open %s!",
cmdname, reqp->nodeid, filename);
return 1;
}
/*
* Stat the file so we get its size to send over first.
*/
if (safesyscall(SYS_fstat, fd, &statbuf) < 0) {
errorc("%s: %s: Could not fstat %s!",
cmdname, reqp->nodeid, filename);
goto bad;
}
/*
* As long as we did the stat, check the uid/gid to make doubly
* sure that we should hand this file out. Either the file has
* to be in the gid of the experiment, or it has to be owned
* by the experiment creator.
*/
if ((grp = getgrnam(reqp->gid)) == NULL) {
error("%s: %s: Could map gid %s!",
cmdname, reqp->nodeid, reqp->gid);
goto bad;
}
if (grp->gr_gid != statbuf.st_gid) {
if ((pwd = getpwnam(reqp->creator)) == NULL) {
error("%s: %s: Could map uid %s!",
cmdname, reqp->nodeid, reqp->creator);
goto bad;
}
if (pwd->pw_uid != statbuf.st_uid) {
error("%s: %s: %s %s has bad uid/gid (%d/%d)\n",
cmdname, reqp->nodeid, filetype, filename,
statbuf.st_uid, statbuf.st_gid);
goto bad;
}
}
cc = statbuf.st_size;
client_writeback(sock, &cc, sizeof(cc), tcp);
/* Leave this logging on all the time for now. */
info("%s: %s: Sending %s (%d): %s\n",
cmdname, reqp->nodeid, filetype, cc, buf);
/*
* Now dump the file.
*/
while (1) {
if ((cc = safesyscall(SYS_read, fd, buf, sizeof(buf))) < 0) {
errorc("Error reading %s: %s", filetype, filename);
goto bad;
}
if (cc == 0)
break;
if (client_writeback(sock, buf, cc, tcp) < 0) {
errorc("Error writing %s data: %s", filetype, filename);
goto bad;
}
}
safesyscall(SYS_close, fd);
return 0;
bad:
safesyscall(SYS_close, fd);
return 1;
}
int nfsdeadfl;
jmp_buf nfsdeadbuf;
static void
nfswentdead()
{
nfsdeadfl = 1;
longjmp(nfsdeadbuf, 1);
}
static int
safesyscall(int sysnum, ...)
{
volatile int retval = 0;
va_list ap;
if (setjmp(nfsdeadbuf) == 0) {
va_start(ap, sysnum);
retval = 0;
nfsdeadfl = 0;
signal(SIGALRM, nfswentdead);
alarm(2);
switch (sysnum) {
case SYS_open:
{
char *path = va_arg(ap, char *);
int flags = va_arg(ap, int);
retval = open(path, flags);
}
break;
case SYS_read:
{
int fd = va_arg(ap, int);
void *buf = va_arg(ap, void *);
size_t nbytes = va_arg(ap, size_t);
retval = read(fd, buf, nbytes);
}
break;
case SYS_fstat:
{
int fd = va_arg(ap, int);
struct stat *sb = va_arg(ap, struct stat *);
retval = fstat(fd, sb);
}
break;
case SYS_close:
{
int fd = va_arg(ap, int);
retval = close(fd);
}
break;
case 696969:
{
char *pathname = va_arg(ap, char *);
char *resolved = va_arg(ap, char *);
retval = (int) realpath(pathname, resolved);
}
break;
default:
break;
}
}
alarm(0);
if (nfsdeadfl) {
error("NFS wend dead");
return -1;
}
return retval;
}
/*
* Return the config for a virtual (jailed) node.
*/
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment