tmcd.c 101 KB
Newer Older
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1
2
/*
 * EMULAB-COPYRIGHT
3
 * Copyright (c) 2000-2003 University of Utah and the Flux Group.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
4
5
6
 * All rights reserved.
 */

7
8
9
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
Mike Hibler's avatar
Mike Hibler committed
10
#include <arpa/inet.h>
11
#include <netdb.h>
Mike Hibler's avatar
Mike Hibler committed
12
#include <ctype.h>
13
#include <stdio.h>
Mike Hibler's avatar
Mike Hibler committed
14
15
16
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
17
18
19
20
#include <syslog.h>
#include <signal.h>
#include <stdarg.h>
#include <assert.h>
21
#include <sys/wait.h>
Leigh B. Stoller's avatar
Leigh B. Stoller committed
22
#include <sys/fcntl.h>
23
24
#include <sys/syscall.h>
#include <sys/stat.h>
25
#include <paths.h>
Austin Clements's avatar
Austin Clements committed
26
#include <setjmp.h>
27
28
#include <pwd.h>
#include <grp.h>
29
30
#include <mysql/mysql.h>
#include "decls.h"
31
#include "config.h"
32
33
#include "ssl.h"
#include "log.h"
34
#include "tbdefs.h"
35

36
37
38
39
#ifdef EVENTSYS
#include "event.h"
#endif

40
41
42
/*
 * XXX This needs to be localized!
 */
43
44
45
#define FSPROJDIR	FSNODE ":" FSDIR_PROJ
#define FSGROUPDIR	FSNODE ":" FSDIR_GROUPS
#define FSUSERDIR	FSNODE ":" FSDIR_USERS
46
47
48
#ifdef  FSDIR_SHARE
#define FSSHAREDIR	FSNODE ":" FSDIR_SHARE
#endif
49
#define PROJDIR		"/proj"
50
#define GROUPDIR	"/groups"
51
#define USERDIR		"/users"
52
#define NETBEDDIR	"/netbed"
53
#define SHAREDIR	"/share"
54
55
#define RELOADPID	"emulab-ops"
#define RELOADEID	"reloading"
Austin Clements's avatar
Austin Clements committed
56
#define FSHOSTID	"/usr/testbed/etc/fshostid"
57
#define DOTSFS		".sfs"
58

59
60
61
#define TESTMODE
#define NETMASK		"255.255.255.0"

62
63
64
#define DISKTYPE	"ad"
#define DISKNUM		0

65
66
67
68
69
70
71
/* Compiled in slothd parameters
 *
 * 1 - reg_interval  2 - agg_interval  3 - load_thresh  
 * 4 - expt_thresh   5 - ctl_thresh
 */
#define SDPARAMS        "reg=300 agg=5 load=1 expt=5 ctl=1000"

72
73
/* Defined in configure and passed in via the makefile */
#define DBNAME_SIZE	64
Austin Clements's avatar
Austin Clements committed
74
#define HOSTID_SIZE	(32+64)
75
76
#define DEFAULT_DBNAME	TBDBNAME

77
int		debug = 0;
78
static int	insecure = 0;
79
static char     dbname[DBNAME_SIZE];
80
static struct in_addr myipaddr;
Austin Clements's avatar
Austin Clements committed
81
static char	fshostid[HOSTID_SIZE];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
82
static int	nodeidtoexp(char *nodeid, char *pid, char *eid, char *gid);
83
static int	nodeidtocontrolnet(char *nodeid, int *net);
84
static int	checkprivkey(struct in_addr, char *);
85
86
87
static void	tcpserver(int sock);
static void	udpserver(int sock);
static int      handle_request(int, struct sockaddr_in *, char *, int);
88
static int	makesockets(int portnum, int *udpsockp, int *tcpsockp);
Mike Hibler's avatar
Mike Hibler committed
89
90
int		client_writeback(int sock, void *buf, int len, int tcp);
void		client_writeback_done(int sock, struct sockaddr_in *client);
91
92
MYSQL_RES *	mydb_query(char *query, int ncols, ...);
int		mydb_update(char *query, ...);
93
static int	safesymlink(char *name1, char *name2);
94

95
/* thread support */
96
#define MAXCHILDREN	20
97
98
#define MINCHILDREN	5
static int	numchildren;
99
static int	maxchildren = 10;
100
static volatile int killme;
101

102
103
104
105
106
107
108
109
110
/*
 * This structure is passed to each request function. The intent is to
 * reduce the number of DB queries per request to a minimum.
 */
typedef struct {
	int		allocated;
	int		jailflag;
	int		isvnode;
	int		islocal;
111
	int		iscontrol;
112
	int		update_accounts;
113
114
	char		nodeid[TBDB_FLEN_NODEID];
	char		vnodeid[TBDB_FLEN_NODEID];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
115
	char		pnodeid[TBDB_FLEN_NODEID]; /* XXX */
116
117
118
119
120
	char		pid[TBDB_FLEN_PID];
	char		eid[TBDB_FLEN_EID];
	char		gid[TBDB_FLEN_GID];
	char		nickname[TBDB_FLEN_VNAME];
	char		type[TBDB_FLEN_NODETYPE];
121
	char		class[TBDB_FLEN_NODECLASS];
122
123
	char		creator[TBDB_FLEN_UID];
	char		swapper[TBDB_FLEN_UID];
124
	char		role[256];
125
126
127
128
129
	char		testdb[256];
} tmcdreq_t;
static int	iptonodeid(struct in_addr, tmcdreq_t *);
static int	checkdbredirect(tmcdreq_t *);

130
131
132
133
134
#ifdef EVENTSYS
int			myevent_send(address_tuple_t address);
static event_handle_t	event_handle = NULL;
#endif

135
136
137
/*
 * Commands we support.
 */
138
139
#define COMMAND_PROTOTYPE(x) \
	static int \
140
	x(int sock, tmcdreq_t *reqp, char *rdata, int tcp, int vers)
141
142

COMMAND_PROTOTYPE(doreboot);
143
COMMAND_PROTOTYPE(donodeid);
144
145
146
147
COMMAND_PROTOTYPE(dostatus);
COMMAND_PROTOTYPE(doifconfig);
COMMAND_PROTOTYPE(doaccounts);
COMMAND_PROTOTYPE(dodelay);
148
COMMAND_PROTOTYPE(dolinkdelay);
149
150
151
152
153
154
155
156
157
158
159
COMMAND_PROTOTYPE(dohosts);
COMMAND_PROTOTYPE(dohostsV2);
COMMAND_PROTOTYPE(dorpms);
COMMAND_PROTOTYPE(dodeltas);
COMMAND_PROTOTYPE(dotarballs);
COMMAND_PROTOTYPE(dostartcmd);
COMMAND_PROTOTYPE(dostartstat);
COMMAND_PROTOTYPE(doready);
COMMAND_PROTOTYPE(doreadycount);
COMMAND_PROTOTYPE(dolog);
COMMAND_PROTOTYPE(domounts);
Austin Clements's avatar
Austin Clements committed
160
COMMAND_PROTOTYPE(dosfshostid);
161
162
163
164
165
166
167
COMMAND_PROTOTYPE(doloadinfo);
COMMAND_PROTOTYPE(doreset);
COMMAND_PROTOTYPE(dorouting);
COMMAND_PROTOTYPE(dotrafgens);
COMMAND_PROTOTYPE(donseconfigs);
COMMAND_PROTOTYPE(dostate);
COMMAND_PROTOTYPE(docreator);
168
COMMAND_PROTOTYPE(dotunnels);
169
COMMAND_PROTOTYPE(dovnodelist);
170
COMMAND_PROTOTYPE(doisalive);
171
COMMAND_PROTOTYPE(doipodinfo);
172
173
174
COMMAND_PROTOTYPE(doatarball);
COMMAND_PROTOTYPE(dontpinfo);
COMMAND_PROTOTYPE(dontpdrift);
175
COMMAND_PROTOTYPE(dojailconfig);
176
COMMAND_PROTOTYPE(doslothdparams);
177
COMMAND_PROTOTYPE(doprogagents);
178
179
180

struct command {
	char	*cmdname;
181
	int    (*func)(int, tmcdreq_t *, char *, int, int);
182
} command_array[] = {
183
	{ "reboot",	doreboot },
184
	{ "nodeid",	donodeid },
185
186
187
188
	{ "status",	dostatus },
	{ "ifconfig",	doifconfig },
	{ "accounts",	doaccounts },
	{ "delay",	dodelay },
189
	{ "linkdelay",	dolinkdelay },
190
191
	{ "hostnamesV2",dohostsV2 },	/* This will go away */
	{ "hostnames",	dohosts },
192
	{ "rpms",	dorpms },
193
	{ "deltas",	dodeltas },
194
	{ "tarballs",	dotarballs },
195
	{ "startupcmd",	dostartcmd },
Mike Hibler's avatar
Mike Hibler committed
196
	{ "startstatus",dostartstat }, /* Leave this before "startstat" */
197
	{ "startstat",	dostartstat },
198
199
	{ "readycount", doreadycount },
	{ "ready",	doready },
Mike Hibler's avatar
Mike Hibler committed
200
	{ "log",	dolog },
201
	{ "mounts",	domounts },
Austin Clements's avatar
Austin Clements committed
202
	{ "sfshostid",	dosfshostid },
203
	{ "loadinfo",	doloadinfo},
Robert Ricci's avatar
Robert Ricci committed
204
	{ "reset",	doreset},
205
	{ "routing",	dorouting},
206
	{ "trafgens",	dotrafgens},
207
	{ "nseconfigs",	donseconfigs},
208
	{ "creator",	docreator},
209
	{ "state",	dostate},
210
	{ "tunnels",	dotunnels},
211
	{ "vnodelist",	dovnodelist},
212
	{ "isalive",	doisalive},
213
	{ "ipodinfo",	doipodinfo},
214
215
216
	{ "ntpinfo",	dontpinfo},
	{ "ntpdrift",	dontpdrift},
	{ "tarball",	doatarball},
217
	{ "jailconfig",	dojailconfig},
218
        { "sdparams",   doslothdparams},
219
        { "programs",   doprogagents},
220
221
222
};
static int numcommands = sizeof(command_array)/sizeof(struct command);

223
224
225
226
char *usagestr = 
 "usage: tmcd [-d] [-p #]\n"
 " -d              Turn on debugging. Multiple -d options increase output\n"
 " -p portnum	   Specify a port number to listen on\n"
227
 " -c num	   Specify number of servers (must be %d <= x <= %d)\n"
228
229
230
231
232
 "\n";

void
usage()
{
233
	fprintf(stderr, usagestr, MINCHILDREN, MAXCHILDREN);
234
235
236
	exit(1);
}

237
238
239
240
241
static void
cleanup()
{
	signal(SIGHUP, SIG_IGN);
	killme = 1;
242
	killpg(0, SIGHUP);
243
244
}

Mike Hibler's avatar
Mike Hibler committed
245
246
int
main(int argc, char **argv)
247
{
248
249
250
251
	int			tcpsock, udpsock, i, ch, foo[4];
	int			alttcpsock, altudpsock;
	int			status, pid;
	int			portnum = TBSERVER_PORT;
252
253
	FILE			*fp;
	char			buf[BUFSIZ];
254
	struct hostent		*he;
255
	extern char		build_info[];
256

257
	while ((ch = getopt(argc, argv, "dp:c:X")) != -1)
258
259
260
261
262
263
		switch(ch) {
		case 'p':
			portnum = atoi(optarg);
			break;
		case 'd':
			debug++;
264
			break;
265
266
267
		case 'c':
			maxchildren = atoi(optarg);
			break;
268
269
270
		case 'X':
			insecure = 1;
			break;
271
272
273
274
275
276
277
278
279
280
		case 'h':
		case '?':
		default:
			usage();
		}
	argc -= optind;
	argv += optind;

	if (argc)
		usage();
281
282
	if (maxchildren < MINCHILDREN || maxchildren > MAXCHILDREN)
		usage();
283

284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
#ifdef  WITHSSL
	if (tmcd_server_sslinit()) {
		error("SSL init failed!\n");
		exit(1);
	}
#endif
	if (debug) 
		loginit(0, 0);
	else {
		/* Become a daemon */
		daemon(0, 0);
		loginit(1, "tmcd");
	}
	info("daemon starting (version %d)\n", CURRENT_VERSION);
	info("%s\n", build_info);
Mike Hibler's avatar
Mike Hibler committed
299

Austin Clements's avatar
Austin Clements committed
300
301
302
303
304
305
306
307
308
309
310
311
312
313
	/*
	 * Get FS's SFS hostid
	 * XXX This approach is somewhat kludgy
	 */
	strcpy(fshostid, "");
	if (access(FSHOSTID,R_OK) == 0) {
		fp = fopen(FSHOSTID, "r");
		if (!fp) {
			error("Failed to get FS's hostid");
		}
		else {
			fgets(fshostid, HOSTID_SIZE, fp);
			if (rindex(fshostid, '\n')) {
				*rindex(fshostid, '\n') = 0;
314
315
316
				if (debug) {
				    info("fshostid: %s\n", fshostid);
				}
Austin Clements's avatar
Austin Clements committed
317
318
319
320
321
322
323
324
325
			}
			else {
				error("fshostid from %s may be corrupt: %s",
				      FSHOSTID, fshostid);
			}
			fclose(fp);
		}
	}
	
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
	/*
	 * 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);

341
342
343
344
345
346
	/*
	 * If we were given a port on the command line, don't open the 
	 * alternate ports
	 */
	if (portnum != TBSERVER_PORT) {
	    if (makesockets(portnum, &udpsock, &tcpsock) < 0) {
347
348
		error("Could not make sockets!");
		exit(1);
349
350
351
352
353
354
355
	    }
	} else {
	    if (makesockets(portnum, &udpsock, &tcpsock) < 0 ||
		makesockets(TBSERVER_PORT2, &altudpsock, &alttcpsock) < 0) {
		    error("Could not make sockets!");
		    exit(1);
	    }
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
	}

	signal(SIGTERM, cleanup);
	signal(SIGINT, cleanup);
	signal(SIGHUP, cleanup);

	/*
	 * Stash the pid away.
	 */
	sprintf(buf, "%s/tmcd.pid", _PATH_VARRUN);
	fp = fopen(buf, "w");
	if (fp != NULL) {
		fprintf(fp, "%d\n", getpid());
		(void) fclose(fp);
	}

	/*
	 * 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,
	 * although this approach *is* rather bogus. 
	 */
	bzero(foo, sizeof(foo));
	while (1) {
		while (!killme && numchildren < maxchildren) {
			int which = 0;
			if (!foo[1])
				which = 1;
383
			else if (!debug && !foo[2])
384
				which = 2;
385
			else if (!debug && !foo[3])
386
				which = 3;
387

388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
			if ((pid = fork()) < 0) {
				errorc("forking server");
				goto done;
			}
			if (pid) {
				foo[which] = pid;
				numchildren++;
				continue;
			}
			/* Child does useful work! Never Returns! */
			signal(SIGTERM, SIG_DFL);
			signal(SIGINT, SIG_DFL);
			signal(SIGHUP, SIG_DFL);
			
			switch (which) {
			case 0: tcpserver(tcpsock);
				break;
			case 1: udpserver(udpsock);
				break;
			case 2: udpserver(altudpsock);
				break;
			case 3: tcpserver(alttcpsock);
				break;
			}
			exit(-1);
		}

		/*
		 * Parent waits.
		 */
		pid = waitpid(-1, &status, 0);
		if (pid < 0) {
			errorc("waitpid failed");
			continue;
		}
423
424
425
426
427
		if( WIFSIGNALED(status) ) {
		  error("server %d exited with signal %d!\n", pid, WTERMSIG(status));
		} else if( WIFEXITED(status) ) {
		  error("server %d exited with status %d!\n", pid, WEXITSTATUS(status));	  
		}
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
		numchildren--;
		for (i = 0; i < (sizeof(foo)/sizeof(int)); i++) {
			if (foo[i] == pid)
				foo[i] = 0;
		}
		if (killme && !numchildren)
			break;
	}
 done:
	CLOSE(tcpsock);
	close(udpsock);
	info("daemon terminating\n");
	exit(0);
}

/*
 * Create sockets on specified port.
 */
static int
makesockets(int portnum, int *udpsockp, int *tcpsockp)
{
	struct sockaddr_in	name;
	int			length, i, udpsock, tcpsock;

Mike Hibler's avatar
Mike Hibler committed
452
	/*
453
	 * Setup TCP socket for incoming connections.
Mike Hibler's avatar
Mike Hibler committed
454
455
	 */

456
	/* Create socket from which to read. */
Mike Hibler's avatar
Mike Hibler committed
457
458
	tcpsock = socket(AF_INET, SOCK_STREAM, 0);
	if (tcpsock < 0) {
459
		pfatal("opening stream socket");
460
461
	}

462
	i = 1;
Mike Hibler's avatar
Mike Hibler committed
463
	if (setsockopt(tcpsock, SOL_SOCKET, SO_REUSEADDR,
464
		       (char *)&i, sizeof(i)) < 0)
465
		pwarning("setsockopt(SO_REUSEADDR)");;
466
467
468
469
	
	/* Create name. */
	name.sin_family = AF_INET;
	name.sin_addr.s_addr = INADDR_ANY;
470
	name.sin_port = htons((u_short) portnum);
Mike Hibler's avatar
Mike Hibler committed
471
	if (bind(tcpsock, (struct sockaddr *) &name, sizeof(name))) {
472
		pfatal("binding stream socket");
473
474
475
	}
	/* Find assigned port value and print it out. */
	length = sizeof(name);
Mike Hibler's avatar
Mike Hibler committed
476
	if (getsockname(tcpsock, (struct sockaddr *) &name, &length)) {
477
		pfatal("getsockname");
478
	}
479
	if (listen(tcpsock, 128) < 0) {
480
		pfatal("listen");
481
	}
482
483
	info("listening on TCP port %d\n", ntohs(name.sin_port));
	
Mike Hibler's avatar
Mike Hibler committed
484
485
486
487
488
489
490
	/*
	 * Setup UDP socket
	 */

	/* Create socket from which to read. */
	udpsock = socket(AF_INET, SOCK_DGRAM, 0);
	if (udpsock < 0) {
491
		pfatal("opening dgram socket");
Mike Hibler's avatar
Mike Hibler committed
492
493
494
495
496
	}

	i = 1;
	if (setsockopt(udpsock, SOL_SOCKET, SO_REUSEADDR,
		       (char *)&i, sizeof(i)) < 0)
497
		pwarning("setsockopt(SO_REUSEADDR)");;
Mike Hibler's avatar
Mike Hibler committed
498
499
500
501
	
	/* Create name. */
	name.sin_family = AF_INET;
	name.sin_addr.s_addr = INADDR_ANY;
502
	name.sin_port = htons((u_short) portnum);
Mike Hibler's avatar
Mike Hibler committed
503
	if (bind(udpsock, (struct sockaddr *) &name, sizeof(name))) {
504
		pfatal("binding dgram socket");
Mike Hibler's avatar
Mike Hibler committed
505
506
507
508
509
	}

	/* Find assigned port value and print it out. */
	length = sizeof(name);
	if (getsockname(udpsock, (struct sockaddr *) &name, &length)) {
510
		pfatal("getsockname");
Mike Hibler's avatar
Mike Hibler committed
511
	}
512
	info("listening on UDP port %d\n", ntohs(name.sin_port));
513

514
515
516
	*tcpsockp = tcpsock;
	*udpsockp = udpsock;
	return 0;
517
}
518

519
/*
520
 * Listen for UDP requests. This is not a secure channel, and so this should
521
522
523
524
525
526
527
528
529
 * eventually be killed off.
 */
static void
udpserver(int sock)
{
	char			buf[MYBUFSIZE];
	struct sockaddr_in	client;
	int			length, cc;
	
530
	info("udpserver starting: pid=%d sock=%d\n", getpid(), sock);
531
532
533
534
535
536

	/*
	 * Wait for udp connections.
	 */
	while (1) {
		length = sizeof(client);		
537
		cc = recvfrom(sock, buf, sizeof(buf) - 1,
538
539
540
			      0, (struct sockaddr *)&client, &length);
		if (cc <= 0) {
			if (cc < 0)
541
542
				errorc("Reading UDP request");
			error("UDP Connection aborted\n");
543
			continue;
544
		}
545
546
547
548
549
550
551
		buf[cc] = '\0';
		handle_request(sock, &client, buf, 0);
	}
	exit(1);
}

/*
552
 * Listen for TCP requests.
553
554
555
556
557
558
559
560
 */
static void
tcpserver(int sock)
{
	char			buf[MYBUFSIZE];
	struct sockaddr_in	client;
	int			length, cc, newsock;
	
561
	info("tcpserver starting: pid=%d sock=%d\n", getpid(), sock);
562
563
564
565
566
567

	/*
	 * Wait for TCP connections.
	 */
	while (1) {
		length  = sizeof(client);
568
		newsock = ACCEPT(sock, (struct sockaddr *)&client, &length);
569
		if (newsock < 0) {
570
			errorc("accepting TCP connection");
571
			continue;
572
		}
Mike Hibler's avatar
Mike Hibler committed
573
574

		/*
575
		 * Read in the command request.
Mike Hibler's avatar
Mike Hibler committed
576
		 */
577
		if ((cc = READ(newsock, buf, sizeof(buf) - 1)) <= 0) {
578
			if (cc < 0)
579
580
581
				errorc("Reading TCP request");
			error("TCP connection aborted\n");
			CLOSE(newsock);
582
			continue;
583
		}
584
585
		buf[cc] = '\0';
		handle_request(newsock, &client, buf, 1);
586
		CLOSE(newsock);
587
588
589
	}
	exit(1);
}
Mike Hibler's avatar
Mike Hibler committed
590

591
592
593
594
static int
handle_request(int sock, struct sockaddr_in *client, char *rdata, int istcp)
{
	struct sockaddr_in redirect_client;
595
	int		   redirect = 0, havekey = 0;
596
	char		   buf[BUFSIZ], *bp, *cp;
597
	char		   privkey[TBDB_FLEN_PRIVKEY];
598
	int		   i, err = 0;
599
	int		   version = DEFAULT_VERSION;
600
601
602
603
604
605
606
	tmcdreq_t	   tmcdreq, *reqp = &tmcdreq;


	/*
	 * Init the req structure.
	 */
	bzero(reqp, sizeof(*reqp));
Mike Hibler's avatar
Mike Hibler committed
607

608
609
610
	/*
	 * Look for special tags. 
	 */
611
	bp = rdata;
612
	while ((bp = strsep(&rdata, " ")) != NULL) {
613
614
615
616
617
618
619
620
621
622
623
624
625
626
		/*
		 * Look for PRIVKEY. 
		 */
		if (sscanf(bp, "PRIVKEY=%64s", buf)) {
			havekey = 1;
			strncpy(privkey, buf, sizeof(privkey));

			if (debug) {
				info("PRIVKEY %s\n", buf);
			}
			continue;
		}


627
628
629
630
631
632
633
		/*
		 * Look for VERSION. 
		 */
		if (sscanf(bp, "VERSION=%d", &i) == 1) {
			version = i;
			continue;
		}
Mike Hibler's avatar
Mike Hibler committed
634

635
636
637
638
639
640
641
642
643
644
		/*
		 * Look for REDIRECT, which is a proxy request for a
		 * client other than the one making the request. Good
		 * for testing. Might become a general tmcd redirect at
		 * some point, so that we can test new tmcds.
		 */
		if (sscanf(bp, "REDIRECT=%30s", buf)) {
			redirect_client = *client;
			redirect        = 1;
			inet_aton(buf, &client->sin_addr);
645

646
647
			info("REDIRECTED from %s to %s\n",
			     inet_ntoa(redirect_client.sin_addr), buf);
648

649
650
651
652
653
654
655
656
657
658
659
			continue;
		}
		
		/*
		 * Look for VNODE. This is used for virtual nodes.
		 * It indicates which of the virtual nodes (on the physical
		 * node) is talking to us. Currently no perm checking.
		 * Very temporary approach; should be done via a per-vnode
		 * cert or a key.
		 */
		if (sscanf(bp, "VNODEID=%30s", buf)) {
660
661
			reqp->isvnode = 1;
			strncpy(reqp->vnodeid, buf, sizeof(reqp->vnodeid));
662

663
664
665
666
667
			if (debug) {
				info("VNODEID %s\n", buf);
			}
			continue;
		}
668

669
670
671
672
673
		/*
		 * An empty token (two delimiters next to each other)
		 * is indicated by a null string. If nothing matched,
		 * and its not an empty token, it must be the actual
		 * command and arguments. Break out.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
674
675
676
		 *
		 * Note that rdata will point to any text after the command.
		 *
677
678
679
680
681
		 */
		if (*bp) {
			break;
		}
	}
682

683
684
685
	/* Start with default DB */
	strcpy(dbname, DEFAULT_DBNAME);

686
	/*
687
	 * Map the ip to a nodeid.
688
	 */
689
	if ((err = iptonodeid(client->sin_addr, reqp))) {
690
691
		if (err == 2) {
			error("No such node vnode mapping %s on %s\n",
692
			      reqp->vnodeid, reqp->nodeid);
693
694
695
696
697
		}
		else {
			error("No such node: %s\n",
			      inet_ntoa(client->sin_addr));
		}
698
699
700
701
702
703
704
705
		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. 
	 */
706
	if (!insecure && redirect &&
707
	    redirect_client.sin_addr.s_addr != myipaddr.s_addr) {
708
709
710
711
712
		char	buf1[32], buf2[32];
		
		strcpy(buf1, inet_ntoa(redirect_client.sin_addr));
		strcpy(buf2, inet_ntoa(client->sin_addr));
			
713
		info("%s INVALID REDIRECT: %s\n", buf1, buf2);
714
715
		goto skipit;
	}
716
717
718
719
720
721

#ifdef  WITHSSL
	/*
	 * If the connection is not SSL, then it must be a local node.
	 */
	if (isssl) {
722
723
724
		if (tmcd_sslverify_client(reqp->nodeid, reqp->class,
					  reqp->type, reqp->islocal)) {
			error("%s: SSL verification failure\n", reqp->nodeid);
725
726
727
728
			if (! redirect)
				goto skipit;
		}
	}
729
	else if (!reqp->islocal) {
730
731
732
		if (!istcp)
			goto execute;
		
733
		error("%s: Remote node connected without SSL!\n",reqp->nodeid);
734
735
		if (!insecure)
			goto skipit;
736
	}
737
738
739
740
741
742
743
744
745
	else if (reqp->iscontrol) {
		if (!istcp)
			goto execute;

		error("%s: Control node connection without SSL!\n",
		      reqp->nodeid);
		if (!insecure)
			goto skipit;
	}
746
747
748
749
#else
	/*
	 * When not compiled for ssl, do not allow remote connections.
	 */
750
	if (!reqp->islocal) {
751
752
		error("%s: Remote node connection not allowed (Define SSL)!\n",
		      reqp->nodeid);
753
754
		if (!insecure)
			goto skipit;
755
	}
756
757
758
759
760
761
	if (reqp->iscontrol) {
		error("%s: Control node connection not allowed "
		      "(Define SSL)!\n", reqp->nodeid);
		if (!insecure)
			goto skipit;
	}
762
763
764
765
#endif
	/*
	 * Check for a redirect using the default DB. This allows
	 * for a simple redirect to a secondary DB for testing.
766
	 * Upon return, the dbname has been changed if redirected.
767
	 */
768
	if (checkdbredirect(reqp)) {
769
770
771
		/* Something went wrong */
		goto skipit;
	}
772

773
774
775
776
777
	/*
	 * Do private key check. widearea nodes must report a private key
	 * It comes over ssl of course. At present we skip this check for
	 * ron nodes. 
	 */
778
	if (!reqp->islocal) {
779
		if (!havekey) {
780
			error("%s: No privkey sent!\n", reqp->nodeid);
781
782
783
784
785
786
787
			/*
			 * Skip. Okay, the problem is that the nodes out
			 * there are not reporting the key!
			goto skipit;
			 */
		}
		else if (checkprivkey(client->sin_addr, privkey)) {
788
789
			error("%s: privkey mismatch: %s!\n",
			      reqp->nodeid, privkey);
790
791
792
793
			goto skipit;
		}
	}

794
795
796
	/*
	 * Figure out what command was given.
	 */
797
 execute:
798
	for (i = 0; i < numcommands; i++)
799
		if (strncmp(bp, command_array[i].cmdname,
800
801
			    strlen(command_array[i].cmdname)) == 0)
			break;
Mike Hibler's avatar
Mike Hibler committed
802

803
	if (i == numcommands) {
804
		info("%s: INVALID REQUEST: %.8s\n", reqp->nodeid, bp);
805
806
		goto skipit;
	}
807

808
809
810
811
812
813
814
815
816
	/*
	 * XXX: We allow remote nodes to use UDP for isalive only!
	 */
	if (!istcp && !reqp->islocal && command_array[i].func != doisalive) {
		error("%s: Invalid request (%s) from remote node using UDP!\n",
		      reqp->nodeid, command_array[i].cmdname);
		goto skipit;
	}

817
818
819
	/*
	 * Execute it.
	 */
820
821
822
823
824
#ifdef	WITHSSL
	cp = isssl ? "ssl:yes" : "ssl:no";
#else
	cp = "";
#endif
825
826
827
828
829
	/*
	 * XXX hack, don't log "log" contents,
	 * both for privacy and to keep our syslog smaller.
	 */
	if (command_array[i].func == dolog)
830
		info("%s: vers:%d %s %s log %d chars\n", reqp->nodeid, version,
831
		     istcp ? "TCP" : "UDP", cp, strlen(rdata));
832
	else
833
		info("%s: vers:%d %s %s %s\n", reqp->nodeid, version,
834
		     istcp ? "TCP" : "UDP", cp, command_array[i].cmdname);
835

836
	err = command_array[i].func(sock, reqp, rdata, istcp, version);
837
838
839

	if (err)
		info("%s: %s: returned %d\n",
840
		     reqp->nodeid, command_array[i].cmdname, err);
841

842
 skipit:
843
844
845
	if (!istcp) 
		client_writeback_done(sock,
				      redirect ? &redirect_client : client);
846
	return 0;
847
848
849
850
851
}

/*
 * Accept notification of reboot. 
 */
852
COMMAND_PROTOTYPE(doreboot)
853
{
854
	/*
855
856
	 * This is now a no-op. The things this used to do are now
	 * done by stated when we hit RELOAD/RELOADDONE state
857
	 */
858
859
	return 0;
}
860

861
862
863
864
865
866
867
/*
 * Return emulab nodeid (not the experimental name).
 */
COMMAND_PROTOTYPE(donodeid)
{
	char		buf[MYBUFSIZE];

868
	sprintf(buf, "%s\n", reqp->nodeid);
869
	client_writeback(sock, buf, strlen(buf), tcp);
870
871
872
873
874
875
	return 0;
}

/*
 * Return status of node. Is it allocated to an experiment, or free.
 */
876
COMMAND_PROTOTYPE(dostatus)
877
{
Mike Hibler's avatar
Mike Hibler committed
878
	char		buf[MYBUFSIZE];
879
880
881
882

	/*
	 * Now check reserved table
	 */
883
884
	if (! reqp->allocated) {
		info("STATUS: %s: Node is free\n", reqp->nodeid);
885
886
887
		strcpy(buf, "FREE\n");
		client_writeback(sock, buf, strlen(buf), tcp);
		return 0;
888
889
	}

890
891
	sprintf(buf, "ALLOCATED=%s/%s NICKNAME=%s\n",
		reqp->pid, reqp->eid, reqp->nickname);
892
893
	client_writeback(sock, buf, strlen(buf), tcp);

894
	info("STATUS: %s: %s", reqp->nodeid, buf);
895
896
897
898
899
900
	return 0;
}

/*
 * Return ifconfig information to client.
 */
901
COMMAND_PROTOTYPE(doifconfig)
902
903
904
{
	MYSQL_RES	*res;	
	MYSQL_ROW	row;
Mike Hibler's avatar
Mike Hibler committed
905
	char		buf[MYBUFSIZE];
906
907
908
909
910
	int		control_net, nrows;

	/*
	 * Now check reserved table
	 */
911
912
	if (! reqp->allocated) {
		info("IFCONFIG: %s: Node is free\n", reqp->nodeid);
913
914
915
		return 1;
	}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
916
917
918
919
920
921
	/*
	 * Virtual nodes, do not return interface table info. No point.
	 */
	if (reqp->isvnode)
		goto doveths;

922
923
924
925
	/*
	 * Need to know the control network for the machine since
	 * we don't want to mess with that.
	 */
926
927
	if (nodeidtocontrolnet(reqp->nodeid, &control_net)) {
		error("IFCONFIG: %s: No Control Network\n", reqp->nodeid);
928
929
930
931
932
933
		return 1;
	}

	/*
	 * Find all the interfaces.
	 */
934
935
	res = mydb_query("select card,IP,IPalias,MAC,current_speed,duplex, "
			 " IPaliases "
936
			 "from interfaces where node_id='%s'",
937
			 7, reqp->nodeid);
938
	if (!res) {
939
940
		error("IFCONFIG: %s: DB Error getting interfaces!\n",
		      reqp->nodeid);
941
942
943
944
		return 1;
	}

	if ((nrows = (int)mysql_num_rows(res)) == 0) {
945
		error("IFCONFIG: %s: No interfaces!\n", reqp->nodeid);
946
947
948
949
950
951
		mysql_free_result(res);
		return 1;
	}
	while (nrows) {
		row = mysql_fetch_row(res);
		if (row[1] && row[1][0]) {
952
953
954
955
			int card    = atoi(row[0]);
			char *speed  = "100";
			char *unit   = "Mbps";
			char *duplex = "full";
956

957
958
			/* Never for the control net; sharks are dead */
			if (card == control_net)
959
960
				goto skipit;

961
			/*
962
			 * Speed and duplex if not the default.
963
			 */
964
965
966
967
			if (row[4] && row[4][0])
				speed = row[4];
			if (row[5] && row[5][0])
				duplex = row[5];
968

969
			/*
970
971
			 * We now use the MAC to determine the interface, but
			 * older images still want that tag at the front.
972
			 */
973
974
975
976
977
			if (vers < 10)
			  sprintf(buf, "INTERFACE=%d ", card);
			else 
			  sprintf(buf, "IFACETYPE=eth ");
			
978
			sprintf(&buf[strlen(buf)],
979
980
				"INET=%s MASK=%s MAC=%s SPEED=%s%s DUPLEX=%s",
				row[1], NETMASK, row[3], speed, unit, duplex);
981

982
983
984
985
986
987
988
989
990
991
992
			/* Tack on IPaliases */
			if (vers >= 8) {
				char *aliases = "";
				
				if (row[6] && row[6][0])
					aliases = row[6];
					
				sprintf(&buf[strlen(buf)],
					" IPALIASES=\"%s\"", aliases);
			}

993
			strcat(buf, "\n");
Mike Hibler's avatar
Mike Hibler committed
994
			client_writeback(sock, buf, strlen(buf), tcp);
995
			info("IFCONFIG: %s", buf);
996
		}
997
	skipit:
998
999
1000
1001
		nrows--;
	}
	mysql_free_result(res);

1002
	/* Veth interfaces are new. */
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1003
 doveths:
1004
	if (vers < 10)
1005
		return 0;
1006

Leigh B. Stoller's avatar
Leigh B. Stoller committed
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
	/*
	 * Outside a vnode, return only those veths that have vnode=NULL,
	 * which indicates its an emulated interface on a physical node. When
	 * inside a vnode, only return veths for which vnode=curvnode,
	 * which are the interfaces that correspond to a jail node.
	 */
	if (reqp->isvnode)
		sprintf(buf, "v.vnode_id='%s'", reqp->vnodeid);
	else
		strcpy(buf, "v.vnode_id is NULL");

1018
1019
1020
1021
1022
1023
1024
	/*
	 * Find all the veth interfaces.
	 */
	res = mydb_query("select v.veth_id,v.IP,v.mac,i.mac "
			 "  from veth_interfaces as v "
			 "left join interfaces as i on "
			 "  i.node_id=v.node_id and i.iface=v.iface "
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1025
1026
			 "where v.node_id='%s' and %s",
			 4, reqp->pnodeid, buf);
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
	if (!res) {
		error("IFCONFIG: %s: DB Error getting veth interfaces!\n",
		      reqp->nodeid);
		return 1;
	}
	if ((nrows = (int)mysql_num_rows(res)) == 0) {
		mysql_free_result(res);
		return 0;
	}
	while (nrows) {
		row = mysql_fetch_row(res);

1039
1040
1041
1042
1043
		/*
		 * Note that PMAC might be NULL, which happens if there is
		 * no underlying phys interface (say, colocated nodes in a
		 * link).
		 */
1044
1045
1046
		sprintf(buf,
			"IFACETYPE=veth "
			"INET=%s MASK=%s ID=%s VMAC=%s PMAC=%s\n",
1047
1048
			row[1], NETMASK, row[0], row[2],
			row[3] ? row[3] : "none");
1049
1050
1051
1052
1053
1054
1055

		client_writeback(sock, buf, strlen(buf), tcp);
		info("IFCONFIG: %s", buf);
		nrows--;
	}
	mysql_free_result(res);

1056
1057
1058
1059
1060
1061
	return 0;
}

/*
 * Return account stuff.
 */
1062
COMMAND_PROTOTYPE(doaccounts)
1063
1064
1065
{
	MYSQL_RES	*res;	
	MYSQL_ROW	row;
Mike Hibler's avatar
Mike Hibler committed
1066
	char		buf[MYBUFSIZE];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1067
	int		nrows, gidint;
1068
	int		tbadmin, didwidearea = 0;
1069

1070
1071
	if (! tcp) {
		error("ACCOUNTS: %s: Cannot give account info out over UDP!\n",
1072
		      reqp->nodeid);
1073
1074
1075
1076
1077
1078
		return 1;
	}

	/*
	 * Now check reserved table
	 */
1079
1080
	if ((reqp->islocal || reqp->isvnode) && !reqp->allocated) {
		error("ACCOUNTS: %s: Node is free\n", reqp->nodeid);
1081
1082
1083
		return 1;
	}

1084
1085
        /*
	 * We need the unix GID and unix name for each group in the project.
1086
	 */
1087
1088
1089
1090
1091
1092
1093
1094
	if (reqp->iscontrol) {
		/*
		 * All groups! 
		 */
		res = mydb_query("select unix_name,unix_gid from groups",
				 2, reqp->pid);
	}
	else if (reqp->islocal || reqp->isvnode) {
1095
1096
		res = mydb_query("select unix_name,unix_gid from groups "
				 "where pid='%s'",
1097
				 2, reqp->pid);
1098
	}
1099
	else if (reqp->jailflag) {
1100
1101
1102
1103
1104
1105
1106
1107