tmcd.c 115 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
123
124
125
126
/* Output macro to check for string overflow */
#define OUTPUT(buf, size, format...) \
({ \
	int __count__ = snprintf((buf), (size), ##format); \
        \
        if (__count__ >= ((size) - 1)) { \
		error("Not enough room in output buffer! line %d.\n", __LINE__);\
		return 1; \
	} \
	__count__; \
})

127
128
129
130
131
132
133
134
/*
 * 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;
135
	int		issubnode;
136
	int		islocal;
137
	int		iscontrol;
138
	int		update_accounts;
139
140
	char		nodeid[TBDB_FLEN_NODEID];
	char		vnodeid[TBDB_FLEN_NODEID];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
141
	char		pnodeid[TBDB_FLEN_NODEID]; /* XXX */
142
143
144
145
146
	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];
147
	char		class[TBDB_FLEN_NODECLASS];
148
149
        char		ptype[TBDB_FLEN_NODETYPE];	/* Of physnode */
	char		pclass[TBDB_FLEN_NODECLASS];	/* Of physnode */
150
151
	char		creator[TBDB_FLEN_UID];
	char		swapper[TBDB_FLEN_UID];
152
	char		syncserver[TBDB_FLEN_VNAME];	/* The vname */
153
	char		keyhash[TBDB_FLEN_PRIVKEY];
154
	char		eventkey[TBDB_FLEN_PRIVKEY];
155
	char		sfshostid[TBDB_FLEN_SFSHOSTID];
156
157
158
159
160
	char		testdb[256];
} tmcdreq_t;
static int	iptonodeid(struct in_addr, tmcdreq_t *);
static int	checkdbredirect(tmcdreq_t *);

161
162
163
164
165
#ifdef EVENTSYS
int			myevent_send(address_tuple_t address);
static event_handle_t	event_handle = NULL;
#endif

166
167
168
/*
 * Commands we support.
 */
169
170
#define COMMAND_PROTOTYPE(x) \
	static int \
171
	x(int sock, tmcdreq_t *reqp, char *rdata, int tcp, int vers)
172
173

COMMAND_PROTOTYPE(doreboot);
174
COMMAND_PROTOTYPE(donodeid);
175
176
177
178
COMMAND_PROTOTYPE(dostatus);
COMMAND_PROTOTYPE(doifconfig);
COMMAND_PROTOTYPE(doaccounts);
COMMAND_PROTOTYPE(dodelay);
179
COMMAND_PROTOTYPE(dolinkdelay);
180
181
182
183
184
185
186
187
188
189
190
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
191
COMMAND_PROTOTYPE(dosfshostid);
192
193
194
195
196
197
198
COMMAND_PROTOTYPE(doloadinfo);
COMMAND_PROTOTYPE(doreset);
COMMAND_PROTOTYPE(dorouting);
COMMAND_PROTOTYPE(dotrafgens);
COMMAND_PROTOTYPE(donseconfigs);
COMMAND_PROTOTYPE(dostate);
COMMAND_PROTOTYPE(docreator);
199
COMMAND_PROTOTYPE(dotunnels);
200
COMMAND_PROTOTYPE(dovnodelist);
201
COMMAND_PROTOTYPE(dosubnodelist);
202
COMMAND_PROTOTYPE(doisalive);
203
COMMAND_PROTOTYPE(doipodinfo);
204
COMMAND_PROTOTYPE(doatarball);
205
COMMAND_PROTOTYPE(doanrpm);
206
207
COMMAND_PROTOTYPE(dontpinfo);
COMMAND_PROTOTYPE(dontpdrift);
208
COMMAND_PROTOTYPE(dojailconfig);
209
COMMAND_PROTOTYPE(doplabconfig);
210
211
COMMAND_PROTOTYPE(dosubconfig);
COMMAND_PROTOTYPE(doixpconfig);
212
COMMAND_PROTOTYPE(doslothdparams);
213
COMMAND_PROTOTYPE(doprogagents);
214
COMMAND_PROTOTYPE(dosyncserver);
215
COMMAND_PROTOTYPE(dokeyhash);
216
COMMAND_PROTOTYPE(doeventkey);
217
COMMAND_PROTOTYPE(dofullconfig);
218

219
220
221
222
223
224
225
226
227
228
229
/*
 * The fullconfig slot determines what routines get called when pushing
 * pushing out a full configuration. Physnodes get slightly different
 * then vnodes, and at some point we might want to distinguish different
 * types of vnodes (jailed, plab).
 */
#define FULLCONFIG_NONE		0x0
#define FULLCONFIG_PHYS		0x1
#define FULLCONFIG_VIRT		0x2
#define FULLCONFIG_ALL		FULLCONFIG_PHYS|FULLCONFIG_VIRT

230
231
struct command {
	char	*cmdname;
232
	int	fullconfig;	
233
	int    (*func)(int, tmcdreq_t *, char *, int, int);
234
} command_array[] = {
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
	{ "reboot",	  FULLCONFIG_NONE, doreboot },
	{ "nodeid",	  FULLCONFIG_ALL,  donodeid },
	{ "status",	  FULLCONFIG_NONE, dostatus },
	{ "ifconfig",	  FULLCONFIG_ALL,  doifconfig },
	{ "accounts",	  FULLCONFIG_ALL,  doaccounts },
	{ "delay",	  FULLCONFIG_ALL,  dodelay },
	{ "linkdelay",	  FULLCONFIG_ALL,  dolinkdelay },
	{ "hostnamesV2",  FULLCONFIG_NONE, dohostsV2 },	/* This will go away */
	{ "hostnames",	  FULLCONFIG_ALL,  dohosts },
	{ "rpms",	  FULLCONFIG_ALL,  dorpms },
	{ "deltas",	  FULLCONFIG_NONE, dodeltas },
	{ "tarballs",	  FULLCONFIG_ALL,  dotarballs },
	{ "startupcmd",	  FULLCONFIG_ALL,  dostartcmd },
	{ "startstatus",  FULLCONFIG_NONE, dostartstat }, /* Before startstat*/
	{ "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},
	{ "reset",	  FULLCONFIG_NONE, doreset},
	{ "routing",	  FULLCONFIG_ALL,  dorouting},
	{ "trafgens",	  FULLCONFIG_ALL,  dotrafgens},
	{ "nseconfigs",	  FULLCONFIG_ALL,  donseconfigs},
	{ "creator",	  FULLCONFIG_ALL,  docreator},
	{ "state",	  FULLCONFIG_NONE, dostate},
	{ "tunnels",	  FULLCONFIG_ALL,  dotunnels},
	{ "vnodelist",	  FULLCONFIG_PHYS, dovnodelist},
	{ "subnodelist",  FULLCONFIG_PHYS, dosubnodelist},
	{ "isalive",	  FULLCONFIG_NONE, doisalive},
	{ "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},
        { "sdparams",     FULLCONFIG_PHYS, doslothdparams},
        { "programs",     FULLCONFIG_ALL,  doprogagents},
        { "syncserver",   FULLCONFIG_ALL,  dosyncserver},
        { "keyhash",      FULLCONFIG_ALL,  dokeyhash},
278
        { "eventkey",     FULLCONFIG_ALL,  doeventkey},
279
        { "fullconfig",   FULLCONFIG_NONE, dofullconfig},
280
281
282
};
static int numcommands = sizeof(command_array)/sizeof(struct command);

283
284
285
286
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"
287
 " -c num	   Specify number of servers (must be %d <= x <= %d)\n"
288
289
290
291
292
 "\n";

void
usage()
{
293
	fprintf(stderr, usagestr, MINCHILDREN, MAXCHILDREN);
294
295
296
	exit(1);
}

297
298
299
300
301
static void
cleanup()
{
	signal(SIGHUP, SIG_IGN);
	killme = 1;
302
	killpg(0, SIGHUP);
303
304
}

305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
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
320
321
int
main(int argc, char **argv)
322
{
323
324
325
326
	int			tcpsock, udpsock, i, ch, foo[4];
	int			alttcpsock, altudpsock;
	int			status, pid;
	int			portnum = TBSERVER_PORT;
327
328
	FILE			*fp;
	char			buf[BUFSIZ];
329
	struct hostent		*he;
330
	extern char		build_info[];
331

332
	while ((ch = getopt(argc, argv, "dp:c:Xv")) != -1)
333
334
335
336
337
338
		switch(ch) {
		case 'p':
			portnum = atoi(optarg);
			break;
		case 'd':
			debug++;
339
			break;
340
341
342
		case 'c':
			maxchildren = atoi(optarg);
			break;
343
344
345
		case 'X':
			insecure = 1;
			break;
346
347
348
		case 'v':
			verbose++;
			break;
349
350
351
352
353
354
355
356
357
358
		case 'h':
		case '?':
		default:
			usage();
		}
	argc -= optind;
	argv += optind;

	if (argc)
		usage();
359
360
	if (maxchildren < MINCHILDREN || maxchildren > MAXCHILDREN)
		usage();
361

362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
#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
377

Austin Clements's avatar
Austin Clements committed
378
379
380
381
382
383
384
385
386
387
388
389
390
391
	/*
	 * 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;
392
393
394
				if (debug) {
				    info("fshostid: %s\n", fshostid);
				}
Austin Clements's avatar
Austin Clements committed
395
396
397
398
399
400
401
402
403
			}
			else {
				error("fshostid from %s may be corrupt: %s",
				      FSHOSTID, fshostid);
			}
			fclose(fp);
		}
	}
	
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
	/*
	 * 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);

419
420
421
422
423
424
	/*
	 * 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) {
425
426
		error("Could not make sockets!");
		exit(1);
427
428
429
430
431
432
433
	    }
	} else {
	    if (makesockets(portnum, &udpsock, &tcpsock) < 0 ||
		makesockets(TBSERVER_PORT2, &altudpsock, &alttcpsock) < 0) {
		    error("Could not make sockets!");
		    exit(1);
	    }
434
435
436
437
438
	}

	signal(SIGTERM, cleanup);
	signal(SIGINT, cleanup);
	signal(SIGHUP, cleanup);
439
440
	signal(SIGUSR1, setverbose);
	signal(SIGUSR2, setverbose);
441
442
443
444

	/*
	 * Stash the pid away.
	 */
445
	mypid = getpid();
446
447
448
	sprintf(buf, "%s/tmcd.pid", _PATH_VARRUN);
	fp = fopen(buf, "w");
	if (fp != NULL) {
449
		fprintf(fp, "%d\n", mypid);
450
451
452
453
454
455
456
457
458
459
460
461
462
463
		(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;
464
			else if (!debug && !foo[2])
465
				which = 2;
466
			else if (!debug && !foo[3])
467
				which = 3;
468

469
470
471
472
473
474
475
476
477
			if ((pid = fork()) < 0) {
				errorc("forking server");
				goto done;
			}
			if (pid) {
				foo[which] = pid;
				numchildren++;
				continue;
			}
478
479
480
481
			/* Poor way of knowing parent/child */
			numchildren = 0;
			mypid = getpid();
			
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
			/* 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;
507
508
509
510
		} 
		if (WIFSIGNALED(status)) {
			error("server %d exited with signal %d!\n",
			      pid, WTERMSIG(status));
511
		}
512
513
514
		else if (WIFEXITED(status)) {
			error("server %d exited with status %d!\n",
			      pid, WEXITSTATUS(status));	  
515
		}
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
		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
540
	/*
541
	 * Setup TCP socket for incoming connections.
Mike Hibler's avatar
Mike Hibler committed
542
543
	 */

544
	/* Create socket from which to read. */
Mike Hibler's avatar
Mike Hibler committed
545
546
	tcpsock = socket(AF_INET, SOCK_STREAM, 0);
	if (tcpsock < 0) {
547
		pfatal("opening stream socket");
548
549
	}

550
	i = 1;
Mike Hibler's avatar
Mike Hibler committed
551
	if (setsockopt(tcpsock, SOL_SOCKET, SO_REUSEADDR,
552
		       (char *)&i, sizeof(i)) < 0)
553
		pwarning("setsockopt(SO_REUSEADDR)");;
554
555
556
557
	
	/* Create name. */
	name.sin_family = AF_INET;
	name.sin_addr.s_addr = INADDR_ANY;
558
	name.sin_port = htons((u_short) portnum);
Mike Hibler's avatar
Mike Hibler committed
559
	if (bind(tcpsock, (struct sockaddr *) &name, sizeof(name))) {
560
		pfatal("binding stream socket");
561
562
563
	}
	/* Find assigned port value and print it out. */
	length = sizeof(name);
Mike Hibler's avatar
Mike Hibler committed
564
	if (getsockname(tcpsock, (struct sockaddr *) &name, &length)) {
565
		pfatal("getsockname");
566
	}
567
	if (listen(tcpsock, 128) < 0) {
568
		pfatal("listen");
569
	}
570
571
	info("listening on TCP port %d\n", ntohs(name.sin_port));
	
Mike Hibler's avatar
Mike Hibler committed
572
573
574
575
576
577
578
	/*
	 * Setup UDP socket
	 */

	/* Create socket from which to read. */
	udpsock = socket(AF_INET, SOCK_DGRAM, 0);
	if (udpsock < 0) {
579
		pfatal("opening dgram socket");
Mike Hibler's avatar
Mike Hibler committed
580
581
582
583
584
	}

	i = 1;
	if (setsockopt(udpsock, SOL_SOCKET, SO_REUSEADDR,
		       (char *)&i, sizeof(i)) < 0)
585
		pwarning("setsockopt(SO_REUSEADDR)");;
Mike Hibler's avatar
Mike Hibler committed
586
587
588
589
	
	/* Create name. */
	name.sin_family = AF_INET;
	name.sin_addr.s_addr = INADDR_ANY;
590
	name.sin_port = htons((u_short) portnum);
Mike Hibler's avatar
Mike Hibler committed
591
	if (bind(udpsock, (struct sockaddr *) &name, sizeof(name))) {
592
		pfatal("binding dgram socket");
Mike Hibler's avatar
Mike Hibler committed
593
594
595
596
597
	}

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

602
603
604
	*tcpsockp = tcpsock;
	*udpsockp = udpsock;
	return 0;
605
}
606

607
/*
608
 * Listen for UDP requests. This is not a secure channel, and so this should
609
610
611
612
613
614
615
616
617
 * eventually be killed off.
 */
static void
udpserver(int sock)
{
	char			buf[MYBUFSIZE];
	struct sockaddr_in	client;
	int			length, cc;
	
618
	info("udpserver starting: pid=%d sock=%d\n", mypid, sock);
619
620
621
622
623
624

	/*
	 * Wait for udp connections.
	 */
	while (1) {
		length = sizeof(client);		
625
		cc = recvfrom(sock, buf, sizeof(buf) - 1,
626
627
628
			      0, (struct sockaddr *)&client, &length);
		if (cc <= 0) {
			if (cc < 0)
629
630
				errorc("Reading UDP request");
			error("UDP Connection aborted\n");
631
			continue;
632
		}
633
634
635
636
637
638
639
		buf[cc] = '\0';
		handle_request(sock, &client, buf, 0);
	}
	exit(1);
}

/*
640
 * Listen for TCP requests.
641
642
643
644
645
646
647
648
 */
static void
tcpserver(int sock)
{
	char			buf[MYBUFSIZE];
	struct sockaddr_in	client;
	int			length, cc, newsock;
	
649
	info("tcpserver starting: pid=%d sock=%d\n", mypid, sock);
650
651
652
653
654
655

	/*
	 * Wait for TCP connections.
	 */
	while (1) {
		length  = sizeof(client);
656
		newsock = ACCEPT(sock, (struct sockaddr *)&client, &length);
657
		if (newsock < 0) {
658
			errorc("accepting TCP connection");
659
			continue;
660
		}
Mike Hibler's avatar
Mike Hibler committed
661
662

		/*
663
		 * Read in the command request.
Mike Hibler's avatar
Mike Hibler committed
664
		 */
665
		if ((cc = READ(newsock, buf, sizeof(buf) - 1)) <= 0) {
666
			if (cc < 0)
667
668
669
				errorc("Reading TCP request");
			error("TCP connection aborted\n");
			CLOSE(newsock);
670
			continue;
671
		}
672
673
		buf[cc] = '\0';
		handle_request(newsock, &client, buf, 1);
674
		CLOSE(newsock);
675
676
677
	}
	exit(1);
}
Mike Hibler's avatar
Mike Hibler committed
678

679
680
681
682
static int
handle_request(int sock, struct sockaddr_in *client, char *rdata, int istcp)
{
	struct sockaddr_in redirect_client;
683
	int		   redirect = 0, havekey = 0;
684
	char		   buf[BUFSIZ], *bp, *cp;
685
	char		   privkey[TBDB_FLEN_PRIVKEY];
686
	int		   i, err = 0;
687
	int		   version = DEFAULT_VERSION;
688
689
	tmcdreq_t	   tmcdreq, *reqp = &tmcdreq;

690
691
	byteswritten = 0;

692
693
694
695
	/*
	 * Init the req structure.
	 */
	bzero(reqp, sizeof(*reqp));
Mike Hibler's avatar
Mike Hibler committed
696

697
698
699
	/*
	 * Look for special tags. 
	 */
700
	bp = rdata;
701
	while ((bp = strsep(&rdata, " ")) != NULL) {
702
703
704
705
706
707
708
709
710
711
712
713
714
715
		/*
		 * Look for PRIVKEY. 
		 */
		if (sscanf(bp, "PRIVKEY=%64s", buf)) {
			havekey = 1;
			strncpy(privkey, buf, sizeof(privkey));

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


716
717
718
719
720
721
722
		/*
		 * Look for VERSION. 
		 */
		if (sscanf(bp, "VERSION=%d", &i) == 1) {
			version = i;
			continue;
		}
Mike Hibler's avatar
Mike Hibler committed
723

724
725
726
727
728
729
730
731
732
733
		/*
		 * 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);
734

735
736
			info("REDIRECTED from %s to %s\n",
			     inet_ntoa(redirect_client.sin_addr), buf);
737

738
739
740
741
742
743
744
745
746
747
748
			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)) {
749
750
			reqp->isvnode = 1;
			strncpy(reqp->vnodeid, buf, sizeof(reqp->vnodeid));
751

752
753
754
755
756
			if (debug) {
				info("VNODEID %s\n", buf);
			}
			continue;
		}
757

758
759
760
761
762
		/*
		 * 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
763
764
765
		 *
		 * Note that rdata will point to any text after the command.
		 *
766
767
768
769
770
		 */
		if (*bp) {
			break;
		}
	}
771

772
773
774
	/* Start with default DB */
	strcpy(dbname, DEFAULT_DBNAME);

775
	/*
776
	 * Map the ip to a nodeid.
777
	 */
778
	if ((err = iptonodeid(client->sin_addr, reqp))) {
779
780
		if (err == 2) {
			error("No such node vnode mapping %s on %s\n",
781
			      reqp->vnodeid, reqp->nodeid);
782
783
784
785
786
		}
		else {
			error("No such node: %s\n",
			      inet_ntoa(client->sin_addr));
		}
787
788
789
790
791
792
793
794
		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. 
	 */
795
	if (!insecure && redirect &&
796
	    redirect_client.sin_addr.s_addr != myipaddr.s_addr) {
797
798
799
800
		char	buf1[32], buf2[32];
		
		strcpy(buf1, inet_ntoa(redirect_client.sin_addr));
		strcpy(buf2, inet_ntoa(client->sin_addr));
801
802
803

		if (verbose)
			info("%s INVALID REDIRECT: %s\n", buf1, buf2);
804
805
		goto skipit;
	}
806
807
808
809
810
811

#ifdef  WITHSSL
	/*
	 * If the connection is not SSL, then it must be a local node.
	 */
	if (isssl) {
812
813
		if (tmcd_sslverify_client(reqp->nodeid, reqp->pclass,
					  reqp->ptype,  reqp->islocal)) {
814
			error("%s: SSL verification failure\n", reqp->nodeid);
815
816
817
818
			if (! redirect)
				goto skipit;
		}
	}
819
	else if (!reqp->islocal) {
820
821
822
		if (!istcp)
			goto execute;
		
823
		error("%s: Remote node connected without SSL!\n",reqp->nodeid);
824
825
		if (!insecure)
			goto skipit;
826
	}
827
828
829
830
831
832
833
834
835
	else if (reqp->iscontrol) {
		if (!istcp)
			goto execute;

		error("%s: Control node connection without SSL!\n",
		      reqp->nodeid);
		if (!insecure)
			goto skipit;
	}
836
837
838
839
#else
	/*
	 * When not compiled for ssl, do not allow remote connections.
	 */
840
	if (!reqp->islocal) {
841
842
		error("%s: Remote node connection not allowed (Define SSL)!\n",
		      reqp->nodeid);
843
844
		if (!insecure)
			goto skipit;
845
	}
846
847
848
849
850
851
	if (reqp->iscontrol) {
		error("%s: Control node connection not allowed "
		      "(Define SSL)!\n", reqp->nodeid);
		if (!insecure)
			goto skipit;
	}
852
853
854
855
#endif
	/*
	 * Check for a redirect using the default DB. This allows
	 * for a simple redirect to a secondary DB for testing.
856
	 * Upon return, the dbname has been changed if redirected.
857
	 */
858
	if (checkdbredirect(reqp)) {
859
860
861
		/* Something went wrong */
		goto skipit;
	}
862

863
864
865
866
867
	/*
	 * 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. 
	 */
868
	if (!reqp->islocal) {
869
		if (!havekey) {
870
			error("%s: No privkey sent!\n", reqp->nodeid);
871
872
873
874
875
876
877
			/*
			 * Skip. Okay, the problem is that the nodes out
			 * there are not reporting the key!
			goto skipit;
			 */
		}
		else if (checkprivkey(client->sin_addr, privkey)) {
878
879
			error("%s: privkey mismatch: %s!\n",
			      reqp->nodeid, privkey);
880
881
882
883
			goto skipit;
		}
	}

884
885
886
	/*
	 * Figure out what command was given.
	 */
887
 execute:
888
	for (i = 0; i < numcommands; i++)
889
		if (strncmp(bp, command_array[i].cmdname,
890
891
			    strlen(command_array[i].cmdname)) == 0)
			break;
Mike Hibler's avatar
Mike Hibler committed
892

893
	if (i == numcommands) {
894
		info("%s: INVALID REQUEST: %.8s\n", reqp->nodeid, bp);
895
896
		goto skipit;
	}
897

898
899
900
901
902
903
904
905
906
	/*
	 * 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;
	}

907
908
909
	/*
	 * Execute it.
	 */
910
#ifdef	WITHSSL
911
	cp = (isssl ? "SSL" : (istcp ? "TCP" : "UDP"));
912
#else
913
	cp = (istcp ? "TCP" : "UDP");
914
#endif
915
916
917
918
919
	/*
	 * XXX hack, don't log "log" contents,
	 * both for privacy and to keep our syslog smaller.
	 */
	if (command_array[i].func == dolog)
920
921
		info("%s: vers:%d %s log %d chars\n",
		     reqp->nodeid, version, cp, strlen(rdata));
922
	else if (command_array[i].func != doisalive || verbose)
923
924
		info("%s: vers:%d %s %s\n", reqp->nodeid,
		     version, cp, command_array[i].cmdname);
925

926
	err = command_array[i].func(sock, reqp, rdata, istcp, version);
927
928
929

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

932
 skipit:
933
934
935
	if (!istcp) 
		client_writeback_done(sock,
				      redirect ? &redirect_client : client);
936
	if (byteswritten)
937
938
		info("%s: %s wrote %d bytes\n",
		     reqp->nodeid, command_array[i].cmdname,
939
940
		     byteswritten);

941
	return 0;
942
943
944
945
946
}

/*
 * Accept notification of reboot. 
 */
947
COMMAND_PROTOTYPE(doreboot)
948
{
949
	/*
950
951
	 * This is now a no-op. The things this used to do are now
	 * done by stated when we hit RELOAD/RELOADDONE state
952
	 */
953
954
	return 0;
}
955

956
957
958
959
960
961
962
/*
 * Return emulab nodeid (not the experimental name).
 */
COMMAND_PROTOTYPE(donodeid)
{
	char		buf[MYBUFSIZE];

963
	OUTPUT(buf, sizeof(buf), "%s\n", reqp->nodeid);
964
	client_writeback(sock, buf, strlen(buf), tcp);
965
966
967
968
969
970
	return 0;
}

/*
 * Return status of node. Is it allocated to an experiment, or free.
 */
971
COMMAND_PROTOTYPE(dostatus)
972
{
Mike Hibler's avatar
Mike Hibler committed
973
	char		buf[MYBUFSIZE];
974
975
976
977

	/*
	 * Now check reserved table
	 */
978
979
	if (! reqp->allocated) {
		info("STATUS: %s: Node is free\n", reqp->nodeid);
980
981
982
		strcpy(buf, "FREE\n");
		client_writeback(sock, buf, strlen(buf), tcp);
		return 0;
983
984
	}

985
986
	OUTPUT(buf, sizeof(buf), "ALLOCATED=%s/%s NICKNAME=%s\n",
	       reqp->pid, reqp->eid, reqp->nickname);
987
988
	client_writeback(sock, buf, strlen(buf), tcp);

989
990
	if (verbose)
		info("STATUS: %s: %s", reqp->nodeid, buf);
991
992
993
994
995
996
	return 0;
}

/*
 * Return ifconfig information to client.
 */
997
COMMAND_PROTOTYPE(doifconfig)
998
999
1000
{
	MYSQL_RES	*res;	
	MYSQL_ROW	row;
1001
	char		buf[MYBUFSIZE], *ebufp = &buf[MYBUFSIZE];
1002
	int		nrows;
1003
1004
1005
1006

	/*
	 * Now check reserved table
	 */
1007
	if (! reqp->allocated) {
1008
1009
		if (verbose)
			info("IFCONFIG: %s: Node is free\n", reqp->nodeid);
1010
1011
1012
		return 1;
	}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
1013
1014
	/*
	 * Virtual nodes, do not return interface table info. No point.
1015
1016
	 * Subnode are slightly different. This test might need to be
	 * smarter?
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1017
	 */
1018
	if (reqp->isvnode && !reqp->issubnode)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1019
1020
		goto doveths;

1021
1022
1023
	/*
	 * Find all the interfaces.
	 */
1024
	res = mydb_query("select card,IP,IPalias,MAC,current_speed,duplex, "
1025
			 " IPaliases,iface,role,mask "
1026
			 "from interfaces where node_id='%s'",
1027
			 10, reqp->nodeid);
1028
	if (!res) {
1029
1030
		error("IFCONFIG: %s: DB Error getting interfaces!\n",
		      reqp->nodeid);
1031
1032
1033
1034
		return 1;
	}

	if ((nrows = (int)mysql_num_rows(res)) == 0) {
1035
		error("IFCONFIG: %s: No interfaces!\n", reqp->nodeid);
1036
1037
1038
1039
1040
1041
		mysql_free_result(res);
		return 1;
	}
	while (nrows) {
		row = mysql_fetch_row(res);
		if (row[1] && row[1][0]) {
1042
1043
1044
			int  card    = atoi(row[0]);
			char *iface  = row[7];
			char *role   = row[8];
1045
1046
1047
			char *speed  = "100";
			char *unit   = "Mbps";
			char *duplex = "full";
1048
			char *bufp   = buf;
1049
			char *mask;
1050

1051
			/* Never for the control net; sharks are dead */
1052
			if (strcmp(role, TBDB_IFACEROLE_EXPERIMENT))
1053
1054
				goto skipit;

1055
1056
1057
			/* Do this after above test to avoid error in log */
			mask = CHECKMASK(row[9]);

1058
			/*
1059
			 * Speed and duplex if not the default.
1060
			 */
1061
1062
1063
1064
			if (row[4] && row[4][0])
				speed = row[4];
			if (row[5] && row[5][0])
				duplex = row[5];
1065

1066
			/*
1067
1068
			 * We now use the MAC to determine the interface, but
			 * older images still want that tag at the front.
1069
			 */
1070
			if (vers < 10)
1071
1072
1073
1074
1075
1076
1077
				bufp += OUTPUT(bufp, ebufp - bufp,
					       "INTERFACE=%d ", card);
			else
				bufp += OUTPUT(bufp, ebufp - bufp,
					       "IFACETYPE=eth ");

			bufp += OUTPUT(bufp, ebufp - bufp,
1078
				"INET=%s MASK=%s MAC=%s SPEED=%s%s DUPLEX=%s",
1079
				row[1], mask, row[3], speed, unit, duplex);
1080

1081
1082
1083
1084
1085
1086
			/* Tack on IPaliases */
			if (vers >= 8) {
				char *aliases = "";
				
				if (row[6] && row[6][0])
					aliases = row[6];
1087
1088
1089

				bufp += OUTPUT(bufp, ebufp - bufp,
					       " IPALIASES=\"%s\"", aliases);
1090
1091
			}

1092
1093
1094
1095
1096
			/*
			 * Tack on iface for IXPs. This should be a flag on
			 * the interface instead of a match against type.
			 */
			if (vers >= 11) {
1097
1098
1099
1100
				bufp += OUTPUT(bufp, ebufp - bufp,
					       " IFACE=%s",
					       (strcmp(reqp->class, "ixp") ?
						"" : iface));
1101
			}
1102
			OUTPUT(bufp, ebufp - bufp, "\n");
Mike Hibler's avatar
Mike Hibler committed
1103
			client_writeback(sock, buf, strlen(buf), tcp);
1104
1105
			if (verbose)
				info("IFCONFIG: %s", buf);
1106
		}
1107
	skipit:
1108
1109
1110
1111
		nrows--;
	}
	mysql_free_result(res);

1112
	/* Veth interfaces are new. */
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1113
 doveths:
1114
	if (vers < 10)
1115
		return 0;
1116