tmcd.c 111 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
#define TESTMODE
60
61
62
63
64
65
66
67
68
69
70
71
#define DEFAULTNETMASK	"255.255.255.0"
/* This can be tossed once all the changes are in place */
static char *
CHECKMASK(char *arg)
{
	if (arg && arg[0])
		return arg;

	error("No netmask defined!\n");
	return DEFAULTNETMASK;
}
/* #define CHECKMASK(arg)  ((arg) && (arg[0]) ? (arg) : DEFAULTNETMASK) */
72

73
74
75
#define DISKTYPE	"ad"
#define DISKNUM		0

76
77
78
79
80
81
82
/* 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"

83
84
/* Defined in configure and passed in via the makefile */
#define DBNAME_SIZE	64
Austin Clements's avatar
Austin Clements committed
85
#define HOSTID_SIZE	(32+64)
86
87
#define DEFAULT_DBNAME	TBDBNAME

88
int		debug = 0;
89
static int	verbose = 0;
90
static int	insecure = 0;
91
static int	byteswritten = 0;
92
static char     dbname[DBNAME_SIZE];
93
static struct in_addr myipaddr;
Austin Clements's avatar
Austin Clements committed
94
static char	fshostid[HOSTID_SIZE];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
95
static int	nodeidtoexp(char *nodeid, char *pid, char *eid, char *gid);
96
static int	checkprivkey(struct in_addr, char *);
97
98
99
static void	tcpserver(int sock);
static void	udpserver(int sock);
static int      handle_request(int, struct sockaddr_in *, char *, int);
100
static int	makesockets(int portnum, int *udpsockp, int *tcpsockp);
Mike Hibler's avatar
Mike Hibler committed
101
102
int		client_writeback(int sock, void *buf, int len, int tcp);
void		client_writeback_done(int sock, struct sockaddr_in *client);
103
104
MYSQL_RES *	mydb_query(char *query, int ncols, ...);
int		mydb_update(char *query, ...);
105
static int	safesymlink(char *name1, char *name2);
106

107
/* thread support */
108
#define MAXCHILDREN	20
109
110
#define MINCHILDREN	5
static int	numchildren;
111
static int	maxchildren = 10;
112
static int	mypid;
113
static volatile int killme;
114

115
116
117
118
119
120
121
122
/*
 * 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;
123
	int		issubnode;
124
	int		islocal;
125
	int		iscontrol;
126
	int		update_accounts;
127
128
	char		nodeid[TBDB_FLEN_NODEID];
	char		vnodeid[TBDB_FLEN_NODEID];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
129
	char		pnodeid[TBDB_FLEN_NODEID]; /* XXX */
130
131
132
133
134
	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];
135
	char		class[TBDB_FLEN_NODECLASS];
136
137
        char		ptype[TBDB_FLEN_NODETYPE];	/* Of physnode */
	char		pclass[TBDB_FLEN_NODECLASS];	/* Of physnode */
138
139
	char		creator[TBDB_FLEN_UID];
	char		swapper[TBDB_FLEN_UID];
140
	char		syncserver[TBDB_FLEN_VNAME];	/* The vname */
141
	char		keyhash[TBDB_FLEN_PRIVKEY];
142
	char		sfshostid[TBDB_FLEN_SFSHOSTID];
143
144
145
146
147
	char		testdb[256];
} tmcdreq_t;
static int	iptonodeid(struct in_addr, tmcdreq_t *);
static int	checkdbredirect(tmcdreq_t *);

148
149
150
151
152
#ifdef EVENTSYS
int			myevent_send(address_tuple_t address);
static event_handle_t	event_handle = NULL;
#endif

153
154
155
/*
 * Commands we support.
 */
156
157
#define COMMAND_PROTOTYPE(x) \
	static int \
158
	x(int sock, tmcdreq_t *reqp, char *rdata, int tcp, int vers)
159
160

COMMAND_PROTOTYPE(doreboot);
161
COMMAND_PROTOTYPE(donodeid);
162
163
164
165
COMMAND_PROTOTYPE(dostatus);
COMMAND_PROTOTYPE(doifconfig);
COMMAND_PROTOTYPE(doaccounts);
COMMAND_PROTOTYPE(dodelay);
166
COMMAND_PROTOTYPE(dolinkdelay);
167
168
169
170
171
172
173
174
175
176
177
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
178
COMMAND_PROTOTYPE(dosfshostid);
179
180
181
182
183
184
185
COMMAND_PROTOTYPE(doloadinfo);
COMMAND_PROTOTYPE(doreset);
COMMAND_PROTOTYPE(dorouting);
COMMAND_PROTOTYPE(dotrafgens);
COMMAND_PROTOTYPE(donseconfigs);
COMMAND_PROTOTYPE(dostate);
COMMAND_PROTOTYPE(docreator);
186
COMMAND_PROTOTYPE(dotunnels);
187
COMMAND_PROTOTYPE(dovnodelist);
188
COMMAND_PROTOTYPE(dosubnodelist);
189
COMMAND_PROTOTYPE(doisalive);
190
COMMAND_PROTOTYPE(doipodinfo);
191
COMMAND_PROTOTYPE(doatarball);
192
COMMAND_PROTOTYPE(doanrpm);
193
194
COMMAND_PROTOTYPE(dontpinfo);
COMMAND_PROTOTYPE(dontpdrift);
195
COMMAND_PROTOTYPE(dojailconfig);
196
COMMAND_PROTOTYPE(doplabconfig);
197
198
COMMAND_PROTOTYPE(dosubconfig);
COMMAND_PROTOTYPE(doixpconfig);
199
COMMAND_PROTOTYPE(doslothdparams);
200
COMMAND_PROTOTYPE(doprogagents);
201
COMMAND_PROTOTYPE(dosyncserver);
202
COMMAND_PROTOTYPE(dokeyhash);
203
COMMAND_PROTOTYPE(dofullconfig);
204
205
206

struct command {
	char	*cmdname;
207
	int	full;
208
	int    (*func)(int, tmcdreq_t *, char *, int, int);
209
} command_array[] = {
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
	{ "reboot",	  0, doreboot },
	{ "nodeid",	  1, donodeid },
	{ "status",	  0, dostatus },
	{ "ifconfig",	  1, doifconfig },
	{ "accounts",	  1, doaccounts },
	{ "delay",	  1, dodelay },
	{ "linkdelay",	  1, dolinkdelay },
	{ "hostnamesV2",  0, dohostsV2 },	/* This will go away */
	{ "hostnames",	  1, dohosts },
	{ "rpms",	  1, dorpms },
	{ "deltas",	  0, dodeltas },
	{ "tarballs",	  1, dotarballs },
	{ "startupcmd",	  1, dostartcmd },
	{ "startstatus",  0, dostartstat }, /* Leave this before "startstat" */
	{ "startstat",	  0, dostartstat },
	{ "readycount",   0, doreadycount },
	{ "ready",	  0, doready },
	{ "log",	  0, dolog },
228
	{ "mounts",	  1, domounts },
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
	{ "sfshostid",	  0, dosfshostid },
	{ "loadinfo",	  0, doloadinfo},
	{ "reset",	  0, doreset},
	{ "routing",	  1, dorouting},
	{ "trafgens",	  1, dotrafgens},
	{ "nseconfigs",	  1, donseconfigs},
	{ "creator",	  1, docreator},
	{ "state",	  0, dostate},
	{ "tunnels",	  1, dotunnels},
	{ "vnodelist",	  1, dovnodelist},
	{ "subnodelist",  1, dosubnodelist},
	{ "isalive",	  0, doisalive},
	{ "ipodinfo",	  0, doipodinfo},
	{ "ntpinfo",	  1, dontpinfo},
	{ "ntpdrift",	  0, dontpdrift},
	{ "tarball",	  0, doatarball},
	{ "rpm",	  0, doanrpm},
246
247
	{ "jailconfig",	  1, dojailconfig},
	{ "plabconfig",	  1, doplabconfig},
248
249
250
251
252
253
	{ "subconfig",	  0, dosubconfig},
        { "sdparams",     1, doslothdparams},
        { "programs",     1, doprogagents},
        { "syncserver",   1, dosyncserver},
        { "keyhash",      1, dokeyhash},
        { "fullconfig",   0, dofullconfig},
254
255
256
};
static int numcommands = sizeof(command_array)/sizeof(struct command);

257
258
259
260
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"
261
 " -c num	   Specify number of servers (must be %d <= x <= %d)\n"
262
263
264
265
266
 "\n";

void
usage()
{
267
	fprintf(stderr, usagestr, MINCHILDREN, MAXCHILDREN);
268
269
270
	exit(1);
}

271
272
273
274
275
static void
cleanup()
{
	signal(SIGHUP, SIG_IGN);
	killme = 1;
276
	killpg(0, SIGHUP);
277
278
}

279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
static void
setverbose(int sig)
{
	signal(sig, SIG_IGN);
	
	if (sig == SIGUSR1)
		verbose = 1;
	else
		verbose = 0;
	/* Just the parent sends this */
	if (numchildren)
		killpg(0, sig);
	signal(sig, setverbose);
}

Mike Hibler's avatar
Mike Hibler committed
294
295
int
main(int argc, char **argv)
296
{
297
298
299
300
	int			tcpsock, udpsock, i, ch, foo[4];
	int			alttcpsock, altudpsock;
	int			status, pid;
	int			portnum = TBSERVER_PORT;
301
302
	FILE			*fp;
	char			buf[BUFSIZ];
303
	struct hostent		*he;
304
	extern char		build_info[];
305

306
	while ((ch = getopt(argc, argv, "dp:c:Xv")) != -1)
307
308
309
310
311
312
		switch(ch) {
		case 'p':
			portnum = atoi(optarg);
			break;
		case 'd':
			debug++;
313
			break;
314
315
316
		case 'c':
			maxchildren = atoi(optarg);
			break;
317
318
319
		case 'X':
			insecure = 1;
			break;
320
321
322
		case 'v':
			verbose++;
			break;
323
324
325
326
327
328
329
330
331
332
		case 'h':
		case '?':
		default:
			usage();
		}
	argc -= optind;
	argv += optind;

	if (argc)
		usage();
333
334
	if (maxchildren < MINCHILDREN || maxchildren > MAXCHILDREN)
		usage();
335

336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
#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
351

Austin Clements's avatar
Austin Clements committed
352
353
354
355
356
357
358
359
360
361
362
363
364
365
	/*
	 * 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;
366
367
368
				if (debug) {
				    info("fshostid: %s\n", fshostid);
				}
Austin Clements's avatar
Austin Clements committed
369
370
371
372
373
374
375
376
377
			}
			else {
				error("fshostid from %s may be corrupt: %s",
				      FSHOSTID, fshostid);
			}
			fclose(fp);
		}
	}
	
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
	/*
	 * 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);

393
394
395
396
397
398
	/*
	 * 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) {
399
400
		error("Could not make sockets!");
		exit(1);
401
402
403
404
405
406
407
	    }
	} else {
	    if (makesockets(portnum, &udpsock, &tcpsock) < 0 ||
		makesockets(TBSERVER_PORT2, &altudpsock, &alttcpsock) < 0) {
		    error("Could not make sockets!");
		    exit(1);
	    }
408
409
410
411
412
	}

	signal(SIGTERM, cleanup);
	signal(SIGINT, cleanup);
	signal(SIGHUP, cleanup);
413
414
	signal(SIGUSR1, setverbose);
	signal(SIGUSR2, setverbose);
415
416
417
418

	/*
	 * Stash the pid away.
	 */
419
	mypid = getpid();
420
421
422
	sprintf(buf, "%s/tmcd.pid", _PATH_VARRUN);
	fp = fopen(buf, "w");
	if (fp != NULL) {
423
		fprintf(fp, "%d\n", mypid);
424
425
426
427
428
429
430
431
432
433
434
435
436
437
		(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;
438
			else if (!debug && !foo[2])
439
				which = 2;
440
			else if (!debug && !foo[3])
441
				which = 3;
442

443
444
445
446
447
448
449
450
451
			if ((pid = fork()) < 0) {
				errorc("forking server");
				goto done;
			}
			if (pid) {
				foo[which] = pid;
				numchildren++;
				continue;
			}
452
453
454
455
			/* Poor way of knowing parent/child */
			numchildren = 0;
			mypid = getpid();
			
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
			/* 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;
481
482
483
484
		} 
		if (WIFSIGNALED(status)) {
			error("server %d exited with signal %d!\n",
			      pid, WTERMSIG(status));
485
		}
486
487
488
		else if (WIFEXITED(status)) {
			error("server %d exited with status %d!\n",
			      pid, WEXITSTATUS(status));	  
489
		}
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
		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
514
	/*
515
	 * Setup TCP socket for incoming connections.
Mike Hibler's avatar
Mike Hibler committed
516
517
	 */

518
	/* Create socket from which to read. */
Mike Hibler's avatar
Mike Hibler committed
519
520
	tcpsock = socket(AF_INET, SOCK_STREAM, 0);
	if (tcpsock < 0) {
521
		pfatal("opening stream socket");
522
523
	}

524
	i = 1;
Mike Hibler's avatar
Mike Hibler committed
525
	if (setsockopt(tcpsock, SOL_SOCKET, SO_REUSEADDR,
526
		       (char *)&i, sizeof(i)) < 0)
527
		pwarning("setsockopt(SO_REUSEADDR)");;
528
529
530
531
	
	/* Create name. */
	name.sin_family = AF_INET;
	name.sin_addr.s_addr = INADDR_ANY;
532
	name.sin_port = htons((u_short) portnum);
Mike Hibler's avatar
Mike Hibler committed
533
	if (bind(tcpsock, (struct sockaddr *) &name, sizeof(name))) {
534
		pfatal("binding stream socket");
535
536
537
	}
	/* Find assigned port value and print it out. */
	length = sizeof(name);
Mike Hibler's avatar
Mike Hibler committed
538
	if (getsockname(tcpsock, (struct sockaddr *) &name, &length)) {
539
		pfatal("getsockname");
540
	}
541
	if (listen(tcpsock, 128) < 0) {
542
		pfatal("listen");
543
	}
544
545
	info("listening on TCP port %d\n", ntohs(name.sin_port));
	
Mike Hibler's avatar
Mike Hibler committed
546
547
548
549
550
551
552
	/*
	 * Setup UDP socket
	 */

	/* Create socket from which to read. */
	udpsock = socket(AF_INET, SOCK_DGRAM, 0);
	if (udpsock < 0) {
553
		pfatal("opening dgram socket");
Mike Hibler's avatar
Mike Hibler committed
554
555
556
557
558
	}

	i = 1;
	if (setsockopt(udpsock, SOL_SOCKET, SO_REUSEADDR,
		       (char *)&i, sizeof(i)) < 0)
559
		pwarning("setsockopt(SO_REUSEADDR)");;
Mike Hibler's avatar
Mike Hibler committed
560
561
562
563
	
	/* Create name. */
	name.sin_family = AF_INET;
	name.sin_addr.s_addr = INADDR_ANY;
564
	name.sin_port = htons((u_short) portnum);
Mike Hibler's avatar
Mike Hibler committed
565
	if (bind(udpsock, (struct sockaddr *) &name, sizeof(name))) {
566
		pfatal("binding dgram socket");
Mike Hibler's avatar
Mike Hibler committed
567
568
569
570
571
	}

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

576
577
578
	*tcpsockp = tcpsock;
	*udpsockp = udpsock;
	return 0;
579
}
580

581
/*
582
 * Listen for UDP requests. This is not a secure channel, and so this should
583
584
585
586
587
588
589
590
591
 * eventually be killed off.
 */
static void
udpserver(int sock)
{
	char			buf[MYBUFSIZE];
	struct sockaddr_in	client;
	int			length, cc;
	
592
	info("udpserver starting: pid=%d sock=%d\n", mypid, sock);
593
594
595
596
597
598

	/*
	 * Wait for udp connections.
	 */
	while (1) {
		length = sizeof(client);		
599
		cc = recvfrom(sock, buf, sizeof(buf) - 1,
600
601
602
			      0, (struct sockaddr *)&client, &length);
		if (cc <= 0) {
			if (cc < 0)
603
604
				errorc("Reading UDP request");
			error("UDP Connection aborted\n");
605
			continue;
606
		}
607
608
609
610
611
612
613
		buf[cc] = '\0';
		handle_request(sock, &client, buf, 0);
	}
	exit(1);
}

/*
614
 * Listen for TCP requests.
615
616
617
618
619
620
621
622
 */
static void
tcpserver(int sock)
{
	char			buf[MYBUFSIZE];
	struct sockaddr_in	client;
	int			length, cc, newsock;
	
623
	info("tcpserver starting: pid=%d sock=%d\n", mypid, sock);
624
625
626
627
628
629

	/*
	 * Wait for TCP connections.
	 */
	while (1) {
		length  = sizeof(client);
630
		newsock = ACCEPT(sock, (struct sockaddr *)&client, &length);
631
		if (newsock < 0) {
632
			errorc("accepting TCP connection");
633
			continue;
634
		}
Mike Hibler's avatar
Mike Hibler committed
635
636

		/*
637
		 * Read in the command request.
Mike Hibler's avatar
Mike Hibler committed
638
		 */
639
		if ((cc = READ(newsock, buf, sizeof(buf) - 1)) <= 0) {
640
			if (cc < 0)
641
642
643
				errorc("Reading TCP request");
			error("TCP connection aborted\n");
			CLOSE(newsock);
644
			continue;
645
		}
646
647
		buf[cc] = '\0';
		handle_request(newsock, &client, buf, 1);
648
		CLOSE(newsock);
649
650
651
	}
	exit(1);
}
Mike Hibler's avatar
Mike Hibler committed
652

653
654
655
656
static int
handle_request(int sock, struct sockaddr_in *client, char *rdata, int istcp)
{
	struct sockaddr_in redirect_client;
657
	int		   redirect = 0, havekey = 0;
658
	char		   buf[BUFSIZ], *bp, *cp;
659
	char		   privkey[TBDB_FLEN_PRIVKEY];
660
	int		   i, err = 0;
661
	int		   version = DEFAULT_VERSION;
662
663
	tmcdreq_t	   tmcdreq, *reqp = &tmcdreq;

664
665
	byteswritten = 0;

666
667
668
669
	/*
	 * Init the req structure.
	 */
	bzero(reqp, sizeof(*reqp));
Mike Hibler's avatar
Mike Hibler committed
670

671
672
673
	/*
	 * Look for special tags. 
	 */
674
	bp = rdata;
675
	while ((bp = strsep(&rdata, " ")) != NULL) {
676
677
678
679
680
681
682
683
684
685
686
687
688
689
		/*
		 * Look for PRIVKEY. 
		 */
		if (sscanf(bp, "PRIVKEY=%64s", buf)) {
			havekey = 1;
			strncpy(privkey, buf, sizeof(privkey));

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


690
691
692
693
694
695
696
		/*
		 * Look for VERSION. 
		 */
		if (sscanf(bp, "VERSION=%d", &i) == 1) {
			version = i;
			continue;
		}
Mike Hibler's avatar
Mike Hibler committed
697

698
699
700
701
702
703
704
705
706
707
		/*
		 * 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);
708

709
710
			info("REDIRECTED from %s to %s\n",
			     inet_ntoa(redirect_client.sin_addr), buf);
711

712
713
714
715
716
717
718
719
720
721
722
			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)) {
723
724
			reqp->isvnode = 1;
			strncpy(reqp->vnodeid, buf, sizeof(reqp->vnodeid));
725

726
727
728
729
730
			if (debug) {
				info("VNODEID %s\n", buf);
			}
			continue;
		}
731

732
733
734
735
736
		/*
		 * 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
737
738
739
		 *
		 * Note that rdata will point to any text after the command.
		 *
740
741
742
743
744
		 */
		if (*bp) {
			break;
		}
	}
745

746
747
748
	/* Start with default DB */
	strcpy(dbname, DEFAULT_DBNAME);

749
	/*
750
	 * Map the ip to a nodeid.
751
	 */
752
	if ((err = iptonodeid(client->sin_addr, reqp))) {
753
754
		if (err == 2) {
			error("No such node vnode mapping %s on %s\n",
755
			      reqp->vnodeid, reqp->nodeid);
756
757
758
759
760
		}
		else {
			error("No such node: %s\n",
			      inet_ntoa(client->sin_addr));
		}
761
762
763
764
765
766
767
768
		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. 
	 */
769
	if (!insecure && redirect &&
770
	    redirect_client.sin_addr.s_addr != myipaddr.s_addr) {
771
772
773
774
		char	buf1[32], buf2[32];
		
		strcpy(buf1, inet_ntoa(redirect_client.sin_addr));
		strcpy(buf2, inet_ntoa(client->sin_addr));
775
776
777

		if (verbose)
			info("%s INVALID REDIRECT: %s\n", buf1, buf2);
778
779
		goto skipit;
	}
780
781
782
783
784
785

#ifdef  WITHSSL
	/*
	 * If the connection is not SSL, then it must be a local node.
	 */
	if (isssl) {
786
787
		if (tmcd_sslverify_client(reqp->nodeid, reqp->pclass,
					  reqp->ptype,  reqp->islocal)) {
788
			error("%s: SSL verification failure\n", reqp->nodeid);
789
790
791
792
			if (! redirect)
				goto skipit;
		}
	}
793
	else if (!reqp->islocal) {
794
795
796
		if (!istcp)
			goto execute;
		
797
		error("%s: Remote node connected without SSL!\n",reqp->nodeid);
798
799
		if (!insecure)
			goto skipit;
800
	}
801
802
803
804
805
806
807
808
809
	else if (reqp->iscontrol) {
		if (!istcp)
			goto execute;

		error("%s: Control node connection without SSL!\n",
		      reqp->nodeid);
		if (!insecure)
			goto skipit;
	}
810
811
812
813
#else
	/*
	 * When not compiled for ssl, do not allow remote connections.
	 */
814
	if (!reqp->islocal) {
815
816
		error("%s: Remote node connection not allowed (Define SSL)!\n",
		      reqp->nodeid);
817
818
		if (!insecure)
			goto skipit;
819
	}
820
821
822
823
824
825
	if (reqp->iscontrol) {
		error("%s: Control node connection not allowed "
		      "(Define SSL)!\n", reqp->nodeid);
		if (!insecure)
			goto skipit;
	}
826
827
828
829
#endif
	/*
	 * Check for a redirect using the default DB. This allows
	 * for a simple redirect to a secondary DB for testing.
830
	 * Upon return, the dbname has been changed if redirected.
831
	 */
832
	if (checkdbredirect(reqp)) {
833
834
835
		/* Something went wrong */
		goto skipit;
	}
836

837
838
839
840
841
	/*
	 * 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. 
	 */
842
	if (!reqp->islocal) {
843
		if (!havekey) {
844
			error("%s: No privkey sent!\n", reqp->nodeid);
845
846
847
848
849
850
851
			/*
			 * Skip. Okay, the problem is that the nodes out
			 * there are not reporting the key!
			goto skipit;
			 */
		}
		else if (checkprivkey(client->sin_addr, privkey)) {
852
853
			error("%s: privkey mismatch: %s!\n",
			      reqp->nodeid, privkey);
854
855
856
857
			goto skipit;
		}
	}

858
859
860
	/*
	 * Figure out what command was given.
	 */
861
 execute:
862
	for (i = 0; i < numcommands; i++)
863
		if (strncmp(bp, command_array[i].cmdname,
864
865
			    strlen(command_array[i].cmdname)) == 0)
			break;
Mike Hibler's avatar
Mike Hibler committed
866

867
	if (i == numcommands) {
868
		info("%s: INVALID REQUEST: %.8s\n", reqp->nodeid, bp);
869
870
		goto skipit;
	}
871

872
873
874
875
876
877
878
879
880
	/*
	 * 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;
	}

881
882
883
	/*
	 * Execute it.
	 */
884
#ifdef	WITHSSL
885
	cp = (isssl ? "SSL" : (istcp ? "TCP" : "UDP"));
886
#else
887
	cp = (istcp ? "TCP" : "UDP");
888
#endif
889
890
891
892
893
	/*
	 * XXX hack, don't log "log" contents,
	 * both for privacy and to keep our syslog smaller.
	 */
	if (command_array[i].func == dolog)
894
895
		info("%s: vers:%d %s log %d chars\n",
		     reqp->nodeid, version, cp, strlen(rdata));
896
	else if (command_array[i].func != doisalive || verbose)
897
898
		info("%s: vers:%d %s %s\n", reqp->nodeid,
		     version, cp, command_array[i].cmdname);
899

900
	err = command_array[i].func(sock, reqp, rdata, istcp, version);
901
902
903

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

906
 skipit:
907
908
909
	if (!istcp) 
		client_writeback_done(sock,
				      redirect ? &redirect_client : client);
910
	if (byteswritten)
911
912
		info("%s: %s wrote %d bytes\n",
		     reqp->nodeid, command_array[i].cmdname,
913
914
		     byteswritten);

915
	return 0;
916
917
918
919
920
}

/*
 * Accept notification of reboot. 
 */
921
COMMAND_PROTOTYPE(doreboot)
922
{
923
	/*
924
925
	 * This is now a no-op. The things this used to do are now
	 * done by stated when we hit RELOAD/RELOADDONE state
926
	 */
927
928
	return 0;
}
929

930
931
932
933
934
935
936
/*
 * Return emulab nodeid (not the experimental name).
 */
COMMAND_PROTOTYPE(donodeid)
{
	char		buf[MYBUFSIZE];

937
	sprintf(buf, "%s\n", reqp->nodeid);
938
	client_writeback(sock, buf, strlen(buf), tcp);
939
940
941
942
943
944
	return 0;
}

/*
 * Return status of node. Is it allocated to an experiment, or free.
 */
945
COMMAND_PROTOTYPE(dostatus)
946
{
Mike Hibler's avatar
Mike Hibler committed
947
	char		buf[MYBUFSIZE];
948
949
950
951

	/*
	 * Now check reserved table
	 */
952
953
	if (! reqp->allocated) {
		info("STATUS: %s: Node is free\n", reqp->nodeid);
954
955
956
		strcpy(buf, "FREE\n");
		client_writeback(sock, buf, strlen(buf), tcp);
		return 0;
957
958
	}

959
960
	sprintf(buf, "ALLOCATED=%s/%s NICKNAME=%s\n",
		reqp->pid, reqp->eid, reqp->nickname);
961
962
	client_writeback(sock, buf, strlen(buf), tcp);

963
964
	if (verbose)
		info("STATUS: %s: %s", reqp->nodeid, buf);
965
966
967
968
969
970
	return 0;
}

/*
 * Return ifconfig information to client.
 */
971
COMMAND_PROTOTYPE(doifconfig)
972
973
974
{
	MYSQL_RES	*res;	
	MYSQL_ROW	row;
Mike Hibler's avatar
Mike Hibler committed
975
	char		buf[MYBUFSIZE];
976
	int		nrows;
977
978
979
980

	/*
	 * Now check reserved table
	 */
981
	if (! reqp->allocated) {
982
983
		if (verbose)
			info("IFCONFIG: %s: Node is free\n", reqp->nodeid);
984
985
986
		return 1;
	}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
987
988
	/*
	 * Virtual nodes, do not return interface table info. No point.
989
990
	 * Subnode are slightly different. This test might need to be
	 * smarter?
Leigh B. Stoller's avatar
Leigh B. Stoller committed
991
	 */
992
	if (reqp->isvnode && !reqp->issubnode)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
993
994
		goto doveths;

995
996
997
	/*
	 * Find all the interfaces.
	 */
998
	res = mydb_query("select card,IP,IPalias,MAC,current_speed,duplex, "
999
			 " IPaliases,iface,role,mask "
1000
			 "from interfaces where node_id='%s'",
1001
			 10, reqp->nodeid);
1002
	if (!res) {
1003
1004
		error("IFCONFIG: %s: DB Error getting interfaces!\n",
		      reqp->nodeid);
1005
1006
1007
1008
		return 1;
	}

	if ((nrows = (int)mysql_num_rows(res)) == 0) {
1009
		error("IFCONFIG: %s: No interfaces!\n", reqp->nodeid);
1010
1011
1012
1013
1014
1015
		mysql_free_result(res);
		return 1;
	}
	while (nrows) {
		row = mysql_fetch_row(res);
		if (row[1] && row[1][0]) {
1016
1017
1018
			int  card    = atoi(row[0]);
			char *iface  = row[7];
			char *role   = row[8];
1019
1020
1021
			char *speed  = "100";
			char *unit   = "Mbps";
			char *duplex = "full";
1022
			char *mask;
1023

1024
			/* Never for the control net; sharks are dead */
1025
			if (strcmp(role, TBDB_IFACEROLE_EXPERIMENT))
1026
1027
				goto skipit;

1028
1029
1030
			/* Do this after above test to avoid error in log */
			mask = CHECKMASK(row[9]);

1031
			/*
1032
			 * Speed and duplex if not the default.
1033
			 */
1034
1035
1036
1037
			if (row[4] && row[4][0])
				speed = row[4];
			if (row[5] && row[5][0])
				duplex = row[5];
1038

1039
			/*
1040
1041
			 * We now use the MAC to determine the interface, but
			 * older images still want that tag at the front.
1042
			 */
1043
1044
1045
1046
1047
			if (vers < 10)
			  sprintf(buf, "INTERFACE=%d ", card);
			else 
			  sprintf(buf, "IFACETYPE=eth ");
			
1048
			sprintf(&buf[strlen(buf)],
1049
				"INET=%s MASK=%s MAC=%s SPEED=%s%s DUPLEX=%s",
1050
				row[1], mask, row[3], speed, unit, duplex);
1051

1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
			/* Tack on IPaliases */
			if (vers >= 8) {
				char *aliases = "";
				
				if (row[6] && row[6][0])
					aliases = row[6];
					
				sprintf(&buf[strlen(buf)],
					" IPALIASES=\"%s\"", aliases);
			}

1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
			/*
			 * Tack on iface for IXPs. This should be a flag on
			 * the interface instead of a match against type.
			 */
			if (vers >= 11) {
				sprintf(&buf[strlen(buf)],
					" IFACE=%s",
					(strcmp(reqp->class, "ixp") ?
					 "" : iface));
			}

1074
			strcat(buf, "\n");
Mike Hibler's avatar
Mike Hibler committed
1075
			client_writeback(sock, buf, strlen(buf), tcp);
1076
1077
			if (verbose)
				info("IFCONFIG: %s", buf);
1078
		}
1079
	skipit:
1080
1081
1082
1083
		nrows--;
	}
	mysql_free_result(res);

1084
	/* Veth interfaces are new. */
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1085
 doveths:
1086
	if (vers < 10)
1087
		return 0;
1088

Leigh B. Stoller's avatar
Leigh B. Stoller committed
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
	/*
	 * 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