tmcd.c 96.4 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 SHAREDIR	"/share"
53
54
#define RELOADPID	"emulab-ops"
#define RELOADEID	"reloading"
Austin Clements's avatar
Austin Clements committed
55
#define FSHOSTID	"/usr/testbed/etc/fshostid"
56
#define DOTSFS		".sfs"
57

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

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

64
65
/* Defined in configure and passed in via the makefile */
#define DBNAME_SIZE	64
Austin Clements's avatar
Austin Clements committed
66
#define HOSTID_SIZE	(32+64)
67
68
#define DEFAULT_DBNAME	TBDBNAME

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

88
/* thread support */
89
#define MAXCHILDREN	20
90
91
#define MINCHILDREN	5
static int	numchildren;
92
static int	maxchildren = 10;
93
static volatile int killme;
94

95
96
97
98
99
100
101
102
103
/*
 * 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;
104
	int		update_accounts;
105
106
107
108
109
110
111
112
113
114
115
116
117
	char		nodeid[TBDB_FLEN_NODEID];
	char		vnodeid[TBDB_FLEN_NODEID];
	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];
	char		class[TBDB_FLEN_NODECLASS];	
	char		testdb[256];
} tmcdreq_t;
static int	iptonodeid(struct in_addr, tmcdreq_t *);
static int	checkdbredirect(tmcdreq_t *);

118
119
120
121
122
#ifdef EVENTSYS
int			myevent_send(address_tuple_t address);
static event_handle_t	event_handle = NULL;
#endif

123
124
125
/*
 * Commands we support.
 */
126
127
#define COMMAND_PROTOTYPE(x) \
	static int \
128
	x(int sock, tmcdreq_t *reqp, char *rdata, int tcp, int vers)
129
130

COMMAND_PROTOTYPE(doreboot);
131
COMMAND_PROTOTYPE(donodeid);
132
133
134
135
COMMAND_PROTOTYPE(dostatus);
COMMAND_PROTOTYPE(doifconfig);
COMMAND_PROTOTYPE(doaccounts);
COMMAND_PROTOTYPE(dodelay);
136
COMMAND_PROTOTYPE(dolinkdelay);
137
138
139
140
141
142
143
144
145
146
147
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
148
COMMAND_PROTOTYPE(dosfshostid);
149
150
151
152
153
154
155
COMMAND_PROTOTYPE(doloadinfo);
COMMAND_PROTOTYPE(doreset);
COMMAND_PROTOTYPE(dorouting);
COMMAND_PROTOTYPE(dotrafgens);
COMMAND_PROTOTYPE(donseconfigs);
COMMAND_PROTOTYPE(dostate);
COMMAND_PROTOTYPE(docreator);
156
COMMAND_PROTOTYPE(dotunnels);
157
COMMAND_PROTOTYPE(dovnodelist);
158
COMMAND_PROTOTYPE(doisalive);
159
COMMAND_PROTOTYPE(doipodinfo);
160
161
162
COMMAND_PROTOTYPE(doatarball);
COMMAND_PROTOTYPE(dontpinfo);
COMMAND_PROTOTYPE(dontpdrift);
163
COMMAND_PROTOTYPE(dojailconfig);
164
165
166

struct command {
	char	*cmdname;
167
	int    (*func)(int, tmcdreq_t *, char *, int, int);
168
} command_array[] = {
169
	{ "reboot",	doreboot },
170
	{ "nodeid",	donodeid },
171
172
173
174
	{ "status",	dostatus },
	{ "ifconfig",	doifconfig },
	{ "accounts",	doaccounts },
	{ "delay",	dodelay },
175
	{ "linkdelay",	dolinkdelay },
176
177
	{ "hostnamesV2",dohostsV2 },	/* This will go away */
	{ "hostnames",	dohosts },
178
	{ "rpms",	dorpms },
179
	{ "deltas",	dodeltas },
180
	{ "tarballs",	dotarballs },
181
	{ "startupcmd",	dostartcmd },
Mike Hibler's avatar
Mike Hibler committed
182
	{ "startstatus",dostartstat }, /* Leave this before "startstat" */
183
	{ "startstat",	dostartstat },
184
185
	{ "readycount", doreadycount },
	{ "ready",	doready },
Mike Hibler's avatar
Mike Hibler committed
186
	{ "log",	dolog },
187
	{ "mounts",	domounts },
Austin Clements's avatar
Austin Clements committed
188
	{ "sfshostid",	dosfshostid },
189
	{ "loadinfo",	doloadinfo},
Robert Ricci's avatar
Robert Ricci committed
190
	{ "reset",	doreset},
191
	{ "routing",	dorouting},
192
	{ "trafgens",	dotrafgens},
193
	{ "nseconfigs",	donseconfigs},
194
	{ "creator",	docreator},
195
	{ "state",	dostate},
196
	{ "tunnels",	dotunnels},
197
	{ "vnodelist",	dovnodelist},
198
	{ "isalive",	doisalive},
199
	{ "ipodinfo",	doipodinfo},
200
201
202
	{ "ntpinfo",	dontpinfo},
	{ "ntpdrift",	dontpdrift},
	{ "tarball",	doatarball},
203
	{ "jailconfig",	dojailconfig},
204
205
206
};
static int numcommands = sizeof(command_array)/sizeof(struct command);

207
208
209
210
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"
211
 " -c num	   Specify number of servers (must be %d <= x <= %d)\n"
212
213
214
215
216
 "\n";

void
usage()
{
217
	fprintf(stderr, usagestr, MINCHILDREN, MAXCHILDREN);
218
219
220
	exit(1);
}

221
222
223
224
225
static void
cleanup()
{
	signal(SIGHUP, SIG_IGN);
	killme = 1;
226
	killpg(0, SIGHUP);
227
228
}

Mike Hibler's avatar
Mike Hibler committed
229
230
int
main(int argc, char **argv)
231
{
232
233
234
235
	int			tcpsock, udpsock, i, ch, foo[4];
	int			alttcpsock, altudpsock;
	int			status, pid;
	int			portnum = TBSERVER_PORT;
236
237
	FILE			*fp;
	char			buf[BUFSIZ];
238
	struct hostent		*he;
239
	extern char		build_info[];
240

241
	while ((ch = getopt(argc, argv, "dp:c:X")) != -1)
242
243
244
245
246
247
		switch(ch) {
		case 'p':
			portnum = atoi(optarg);
			break;
		case 'd':
			debug++;
248
			break;
249
250
251
		case 'c':
			maxchildren = atoi(optarg);
			break;
252
253
254
255
256
#ifdef LBS
		case 'X':
			insecure = 1;
			break;
#endif
257
258
259
260
261
262
263
264
265
266
		case 'h':
		case '?':
		default:
			usage();
		}
	argc -= optind;
	argv += optind;

	if (argc)
		usage();
267
268
	if (maxchildren < MINCHILDREN || maxchildren > MAXCHILDREN)
		usage();
269

270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
#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
285

Austin Clements's avatar
Austin Clements committed
286
287
288
289
290
291
292
293
294
295
296
297
298
299
	/*
	 * 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;
300
301
302
				if (debug) {
				    info("fshostid: %s\n", fshostid);
				}
Austin Clements's avatar
Austin Clements committed
303
304
305
306
307
308
309
310
311
			}
			else {
				error("fshostid from %s may be corrupt: %s",
				      FSHOSTID, fshostid);
			}
			fclose(fp);
		}
	}
	
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
	/*
	 * 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);

327
328
329
330
331
332
	/*
	 * 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) {
333
334
		error("Could not make sockets!");
		exit(1);
335
336
337
338
339
340
341
	    }
	} else {
	    if (makesockets(portnum, &udpsock, &tcpsock) < 0 ||
		makesockets(TBSERVER_PORT2, &altudpsock, &alttcpsock) < 0) {
		    error("Could not make sockets!");
		    exit(1);
	    }
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
	}

	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;
369
			else if (!debug && !foo[2])
370
				which = 2;
371
			else if (!debug && !foo[3])
372
				which = 3;
373

374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
			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;
		}
409
410
411
412
413
		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));	  
		}
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
		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
438
	/*
439
	 * Setup TCP socket for incoming connections.
Mike Hibler's avatar
Mike Hibler committed
440
441
	 */

442
	/* Create socket from which to read. */
Mike Hibler's avatar
Mike Hibler committed
443
444
	tcpsock = socket(AF_INET, SOCK_STREAM, 0);
	if (tcpsock < 0) {
445
		pfatal("opening stream socket");
446
447
	}

448
	i = 1;
Mike Hibler's avatar
Mike Hibler committed
449
	if (setsockopt(tcpsock, SOL_SOCKET, SO_REUSEADDR,
450
		       (char *)&i, sizeof(i)) < 0)
451
		pwarning("setsockopt(SO_REUSEADDR)");;
452
453
454
455
	
	/* Create name. */
	name.sin_family = AF_INET;
	name.sin_addr.s_addr = INADDR_ANY;
456
	name.sin_port = htons((u_short) portnum);
Mike Hibler's avatar
Mike Hibler committed
457
	if (bind(tcpsock, (struct sockaddr *) &name, sizeof(name))) {
458
		pfatal("binding stream socket");
459
460
461
	}
	/* Find assigned port value and print it out. */
	length = sizeof(name);
Mike Hibler's avatar
Mike Hibler committed
462
	if (getsockname(tcpsock, (struct sockaddr *) &name, &length)) {
463
		pfatal("getsockname");
464
	}
465
	if (listen(tcpsock, 128) < 0) {
466
		pfatal("listen");
467
	}
468
469
	info("listening on TCP port %d\n", ntohs(name.sin_port));
	
Mike Hibler's avatar
Mike Hibler committed
470
471
472
473
474
475
476
	/*
	 * Setup UDP socket
	 */

	/* Create socket from which to read. */
	udpsock = socket(AF_INET, SOCK_DGRAM, 0);
	if (udpsock < 0) {
477
		pfatal("opening dgram socket");
Mike Hibler's avatar
Mike Hibler committed
478
479
480
481
482
	}

	i = 1;
	if (setsockopt(udpsock, SOL_SOCKET, SO_REUSEADDR,
		       (char *)&i, sizeof(i)) < 0)
483
		pwarning("setsockopt(SO_REUSEADDR)");;
Mike Hibler's avatar
Mike Hibler committed
484
485
486
487
	
	/* Create name. */
	name.sin_family = AF_INET;
	name.sin_addr.s_addr = INADDR_ANY;
488
	name.sin_port = htons((u_short) portnum);
Mike Hibler's avatar
Mike Hibler committed
489
	if (bind(udpsock, (struct sockaddr *) &name, sizeof(name))) {
490
		pfatal("binding dgram socket");
Mike Hibler's avatar
Mike Hibler committed
491
492
493
494
495
	}

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

500
501
502
	*tcpsockp = tcpsock;
	*udpsockp = udpsock;
	return 0;
503
}
504

505
/*
506
 * Listen for UDP requests. This is not a secure channel, and so this should
507
508
509
510
511
512
513
514
515
 * eventually be killed off.
 */
static void
udpserver(int sock)
{
	char			buf[MYBUFSIZE];
	struct sockaddr_in	client;
	int			length, cc;
	
516
	info("udpserver starting: pid=%d sock=%d\n", getpid(), sock);
517
518
519
520
521
522

	/*
	 * Wait for udp connections.
	 */
	while (1) {
		length = sizeof(client);		
523
		cc = recvfrom(sock, buf, sizeof(buf) - 1,
524
525
526
			      0, (struct sockaddr *)&client, &length);
		if (cc <= 0) {
			if (cc < 0)
527
528
				errorc("Reading UDP request");
			error("UDP Connection aborted\n");
529
			continue;
530
		}
531
532
533
534
535
536
537
		buf[cc] = '\0';
		handle_request(sock, &client, buf, 0);
	}
	exit(1);
}

/*
538
 * Listen for TCP requests.
539
540
541
542
543
544
545
546
 */
static void
tcpserver(int sock)
{
	char			buf[MYBUFSIZE];
	struct sockaddr_in	client;
	int			length, cc, newsock;
	
547
	info("tcpserver starting: pid=%d sock=%d\n", getpid(), sock);
548
549
550
551
552
553

	/*
	 * Wait for TCP connections.
	 */
	while (1) {
		length  = sizeof(client);
554
		newsock = ACCEPT(sock, (struct sockaddr *)&client, &length);
555
		if (newsock < 0) {
556
			errorc("accepting TCP connection");
557
			continue;
558
		}
Mike Hibler's avatar
Mike Hibler committed
559
560

		/*
561
		 * Read in the command request.
Mike Hibler's avatar
Mike Hibler committed
562
		 */
563
		if ((cc = READ(newsock, buf, sizeof(buf) - 1)) <= 0) {
564
			if (cc < 0)
565
566
567
				errorc("Reading TCP request");
			error("TCP connection aborted\n");
			CLOSE(newsock);
568
			continue;
569
		}
570
571
		buf[cc] = '\0';
		handle_request(newsock, &client, buf, 1);
572
		CLOSE(newsock);
573
574
575
	}
	exit(1);
}
Mike Hibler's avatar
Mike Hibler committed
576

577
578
579
580
static int
handle_request(int sock, struct sockaddr_in *client, char *rdata, int istcp)
{
	struct sockaddr_in redirect_client;
581
	int		   redirect = 0, havekey = 0;
582
	char		   buf[BUFSIZ], *bp, *cp;
583
	char		   privkey[TBDB_FLEN_PRIVKEY];
584
	int		   i, err = 0;
585
	int		   version = DEFAULT_VERSION;
586
587
588
589
590
591
592
	tmcdreq_t	   tmcdreq, *reqp = &tmcdreq;


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

594
595
596
	/*
	 * Look for special tags. 
	 */
597
	bp = rdata;
598
	while ((bp = strsep(&rdata, " ")) != NULL) {
599
600
601
602
603
604
605
606
607
608
609
610
611
612
		/*
		 * Look for PRIVKEY. 
		 */
		if (sscanf(bp, "PRIVKEY=%64s", buf)) {
			havekey = 1;
			strncpy(privkey, buf, sizeof(privkey));

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


613
614
615
616
617
618
619
		/*
		 * Look for VERSION. 
		 */
		if (sscanf(bp, "VERSION=%d", &i) == 1) {
			version = i;
			continue;
		}
Mike Hibler's avatar
Mike Hibler committed
620

621
622
623
624
625
626
627
628
629
630
		/*
		 * 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);
631

632
633
			info("REDIRECTED from %s to %s\n",
			     inet_ntoa(redirect_client.sin_addr), buf);
634

635
636
637
638
639
640
641
642
643
644
645
			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)) {
646
647
			reqp->isvnode = 1;
			strncpy(reqp->vnodeid, buf, sizeof(reqp->vnodeid));
648

649
650
651
652
653
			if (debug) {
				info("VNODEID %s\n", buf);
			}
			continue;
		}
654

655
656
657
658
659
		/*
		 * 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
660
661
662
		 *
		 * Note that rdata will point to any text after the command.
		 *
663
664
665
666
667
		 */
		if (*bp) {
			break;
		}
	}
668

669
670
671
	/* Start with default DB */
	strcpy(dbname, DEFAULT_DBNAME);

672
	/*
673
	 * Map the ip to a nodeid.
674
	 */
675
	if ((err = iptonodeid(client->sin_addr, reqp))) {
676
677
		if (err == 2) {
			error("No such node vnode mapping %s on %s\n",
678
			      reqp->vnodeid, reqp->nodeid);
679
680
681
682
683
		}
		else {
			error("No such node: %s\n",
			      inet_ntoa(client->sin_addr));
		}
684
685
686
687
688
689
690
691
		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. 
	 */
692
	if (!insecure && redirect &&
693
	    redirect_client.sin_addr.s_addr != myipaddr.s_addr) {
694
695
696
697
698
		char	buf1[32], buf2[32];
		
		strcpy(buf1, inet_ntoa(redirect_client.sin_addr));
		strcpy(buf2, inet_ntoa(client->sin_addr));
			
699
		info("%s INVALID REDIRECT: %s\n", buf1, buf2);
700
701
		goto skipit;
	}
702
703
704
705
706
707

#ifdef  WITHSSL
	/*
	 * If the connection is not SSL, then it must be a local node.
	 */
	if (isssl) {
708
709
710
		if (tmcd_sslverify_client(reqp->nodeid, reqp->class,
					  reqp->type, reqp->islocal)) {
			error("%s: SSL verification failure\n", reqp->nodeid);
711
712
713
714
			if (! redirect)
				goto skipit;
		}
	}
715
	else if (!reqp->islocal) {
716
717
718
		if (!istcp)
			goto execute;
		
719
		error("%s: Remote node connected without SSL!\n",reqp->nodeid);
720
721
		if (!insecure)
			goto skipit;
722
723
724
725
726
	}
#else
	/*
	 * When not compiled for ssl, do not allow remote connections.
	 */
727
	if (!reqp->islocal) {
728
729
		error("%s: Remote node connection not allowed (Define SSL)!\n",
		      reqp->nodeid);
730
731
		if (!insecure)
			goto skipit;
732
	}
733
734
735
736
#endif
	/*
	 * Check for a redirect using the default DB. This allows
	 * for a simple redirect to a secondary DB for testing.
737
	 * Upon return, the dbname has been changed if redirected.
738
	 */
739
	if (checkdbredirect(reqp)) {
740
741
742
		/* Something went wrong */
		goto skipit;
	}
743

744
745
746
747
748
	/*
	 * 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. 
	 */
749
	if (!reqp->islocal) {
750
		if (!havekey) {
751
			error("%s: No privkey sent!\n", reqp->nodeid);
752
753
754
755
756
757
758
			/*
			 * Skip. Okay, the problem is that the nodes out
			 * there are not reporting the key!
			goto skipit;
			 */
		}
		else if (checkprivkey(client->sin_addr, privkey)) {
759
760
			error("%s: privkey mismatch: %s!\n",
			      reqp->nodeid, privkey);
761
762
763
764
			goto skipit;
		}
	}

765
766
767
	/*
	 * Figure out what command was given.
	 */
768
 execute:
769
	for (i = 0; i < numcommands; i++)
770
		if (strncmp(bp, command_array[i].cmdname,
771
772
			    strlen(command_array[i].cmdname)) == 0)
			break;
Mike Hibler's avatar
Mike Hibler committed
773

774
	if (i == numcommands) {
775
		info("%s: INVALID REQUEST: %.8s\n", reqp->nodeid, bp);
776
777
		goto skipit;
	}
778

779
780
781
782
783
784
785
786
787
	/*
	 * 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;
	}

788
789
790
	/*
	 * Execute it.
	 */
791
792
793
794
795
#ifdef	WITHSSL
	cp = isssl ? "ssl:yes" : "ssl:no";
#else
	cp = "";
#endif
796
797
798
799
800
	/*
	 * XXX hack, don't log "log" contents,
	 * both for privacy and to keep our syslog smaller.
	 */
	if (command_array[i].func == dolog)
801
		info("%s: vers:%d %s %s log %d chars\n", reqp->nodeid, version,
802
		     istcp ? "TCP" : "UDP", cp, strlen(rdata));
803
	else
804
		info("%s: vers:%d %s %s %s\n", reqp->nodeid, version,
805
		     istcp ? "TCP" : "UDP", cp, command_array[i].cmdname);
806

807
	err = command_array[i].func(sock, reqp, rdata, istcp, version);
808
809
810

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

813
 skipit:
814
815
816
	if (!istcp) 
		client_writeback_done(sock,
				      redirect ? &redirect_client : client);
817
	return 0;
818
819
820
821
822
}

/*
 * Accept notification of reboot. 
 */
823
COMMAND_PROTOTYPE(doreboot)
824
{
825
	/*
826
827
	 * This is now a no-op. The things this used to do are now
	 * done by stated when we hit RELOAD/RELOADDONE state
828
	 */
829
830
	return 0;
}
831

832
833
834
835
836
837
838
/*
 * Return emulab nodeid (not the experimental name).
 */
COMMAND_PROTOTYPE(donodeid)
{
	char		buf[MYBUFSIZE];

839
	sprintf(buf, "%s\n", reqp->nodeid);
840
	client_writeback(sock, buf, strlen(buf), tcp);
841
842
843
844
845
846
	return 0;
}

/*
 * Return status of node. Is it allocated to an experiment, or free.
 */
847
COMMAND_PROTOTYPE(dostatus)
848
{
Mike Hibler's avatar
Mike Hibler committed
849
	char		buf[MYBUFSIZE];
850
851
852
853

	/*
	 * Now check reserved table
	 */
854
855
	if (! reqp->allocated) {
		info("STATUS: %s: Node is free\n", reqp->nodeid);
856
857
858
		strcpy(buf, "FREE\n");
		client_writeback(sock, buf, strlen(buf), tcp);
		return 0;
859
860
	}

861
862
	sprintf(buf, "ALLOCATED=%s/%s NICKNAME=%s\n",
		reqp->pid, reqp->eid, reqp->nickname);
863
864
	client_writeback(sock, buf, strlen(buf), tcp);

865
	info("STATUS: %s: %s", reqp->nodeid, buf);
866
867
868
869
870
871
	return 0;
}

/*
 * Return ifconfig information to client.
 */
872
COMMAND_PROTOTYPE(doifconfig)
873
874
875
{
	MYSQL_RES	*res;	
	MYSQL_ROW	row;
Mike Hibler's avatar
Mike Hibler committed
876
	char		buf[MYBUFSIZE];
877
878
879
880
881
	int		control_net, nrows;

	/*
	 * Now check reserved table
	 */
882
883
	if (! reqp->allocated) {
		info("IFCONFIG: %s: Node is free\n", reqp->nodeid);
884
885
886
887
888
889
890
		return 1;
	}

	/*
	 * Need to know the control network for the machine since
	 * we don't want to mess with that.
	 */
891
892
	if (nodeidtocontrolnet(reqp->nodeid, &control_net)) {
		error("IFCONFIG: %s: No Control Network\n", reqp->nodeid);
893
894
895
896
897
898
		return 1;
	}

	/*
	 * Find all the interfaces.
	 */
899
900
	res = mydb_query("select card,IP,IPalias,MAC,current_speed,duplex, "
			 " IPaliases "
901
			 "from interfaces where node_id='%s'",
902
			 7, reqp->nodeid);
903
	if (!res) {
904
905
		error("IFCONFIG: %s: DB Error getting interfaces!\n",
		      reqp->nodeid);
906
907
908
909
		return 1;
	}

	if ((nrows = (int)mysql_num_rows(res)) == 0) {
910
		error("IFCONFIG: %s: No interfaces!\n", reqp->nodeid);
911
912
913
914
915
916
		mysql_free_result(res);
		return 1;
	}
	while (nrows) {
		row = mysql_fetch_row(res);
		if (row[1] && row[1][0]) {
917
918
919
920
			int card    = atoi(row[0]);
			char *speed  = "100";
			char *unit   = "Mbps";
			char *duplex = "full";
921

922
923
924
925
926
927
			/*
			 * INTERFACE can go away when all machines running
			 * updated (MAC based) setup. Right now MAC is at
			 * the end (see below) cause of the sharks, but they
			 * should be dead soon too.
			 */
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
			sprintf(buf, "INTERFACE=%d INET=%s MASK=%s",
				card, row[1], NETMASK);

			/*
			 * The point of this sillyness is to look for the
			 * special Shark case. The sharks have only one
			 * interface, so we use an IPalias on what we call
			 * the "control" interface. This test prevents us
			 * from returning an ifconfig line for the control
			 * interface, unless it has an IP alias. I could
			 * change the setup scripts to ignore this case on
			 * the PCs. Might end up doing that at some point. 
			 */
			if (row[2] && row[2][0]) {
				strcat(buf, " IPALIAS=");
				strcat(buf, row[2]);
			}
			else if (card == control_net)
				goto skipit;

948
949
950
951
952
953
954
			/*
			 * Tack on MAC, which should go up above after
			 * Sharks are sunk.
			 */
			strcat(buf, " MAC=");
			strcat(buf, row[3]);

955
956
957
958
959
960
961
962
963
964
965
966
			/*
			 * Tack on speed and duplex. 
			 */
			if (row[4] && row[4][0]) {
				speed = row[4];
			}
			if (row[5] && row[5][0]) {
				duplex = row[5];
			}
			sprintf(&buf[strlen(buf)],
				" SPEED=%s%s DUPLEX=%s", speed, unit, duplex);

967
968
969
970
971
972
973
974
975
976
977
			/* Tack on IPaliases */
			if (vers >= 8) {
				char *aliases = "";
				
				if (row[6] && row[6][0])
					aliases = row[6];
					
				sprintf(&buf[strlen(buf)],
					" IPALIASES=\"%s\"", aliases);
			}

978
			strcat(buf, "\n");
Mike Hibler's avatar
Mike Hibler committed
979
			client_writeback(sock, buf, strlen(buf), tcp);
980
			info("IFCONFIG: %s", buf);
981
		}
982
	skipit:
983
984
985
986
987
988
989
990
991
992
		nrows--;
	}
	mysql_free_result(res);

	return 0;
}

/*
 * Return account stuff.
 */
993
COMMAND_PROTOTYPE(doaccounts)
994
995
996
{
	MYSQL_RES	*res;	
	MYSQL_ROW	row;
Mike Hibler's avatar
Mike Hibler committed
997
	char		buf[MYBUFSIZE];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
998
	int		nrows, gidint;
999
	int		tbadmin, didwidearea = 0;
1000

1001
1002
	if (! tcp) {
		error("ACCOUNTS: %s: Cannot give account info out over UDP!\n",
1003
		      reqp->nodeid);
1004
1005
1006
1007
1008
1009
		return 1;
	}

	/*
	 * Now check reserved table
	 */
1010
1011
	if ((reqp->islocal || reqp->isvnode) && !reqp->allocated) {
		error("ACCOUNTS: %s: Node is free\n", reqp->nodeid);
1012
1013
1014
		return 1;
	}

1015
1016
        /*
	 * We need the unix GID and unix name for each group in the project.
1017
	 */
1018
	if (reqp->islocal || reqp->isvnode) {
1019
1020
		res = mydb_query("select unix_name,unix_gid from groups "
				 "where pid='%s'",
1021
				 2, reqp->pid);
1022
	}
1023
	else if (reqp->jailflag) {
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
		/*
		 * A remote node, doing jails. We still want to return
		 * a group for the admin people who get accounts outside
		 * the jails. Lets use the same query as above for now,
		 * but switch over to emulab-ops. 
		 */
		res = mydb_query("select unix_name,unix_gid from groups "
				 "where pid='%s'",
				 2, RELOADPID);
	}
1034
1035
	else {
		/*
1036
1037
		 * XXX - Old style node, not doing jails.
		 *
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
		 * Temporary hack until we figure out the right model for
		 * remote nodes. For now, we use the pcremote-ok slot in
		 * in the project table to determine what remote nodes are
		 * okay'ed for the project. If connecting node type is in
		 * that list, then return all of the project groups, for
		 * each project that is allowed to get accounts on the type.
		 */
		res = mydb_query("select g.unix_name,g.unix_gid "
				 "  from projects as p "
				 "left join groups as g on p.pid=g.pid "
1048
1049
				 "where p.approved!=0 and "
				 "      FIND_IN_SET('%s',pcremote_ok)>0",
1050
				 2, reqp->type);
1051
	}
1052
	if (!res) {
1053
		error("ACCOUNTS: %s: DB Error getting gids!\n", reqp->pid);
1054
1055
1056
1057
		return 1;
	}

	if ((nrows = (int)mysql_num_rows(res)) == 0) {
1058
		error("ACCOUNTS: %s: No Project!\n", reqp->pid);
1059
1060
1061
1062
		mysql_free_result(res);
		return 1;
	}

1063
1064
1065
	while (nrows) {
		row = mysql_fetch_row(res);
		if (!row[1] || !row[1][1]) {
1066
			error("ACCOUNTS: %s: No Project GID!\n", reqp->pid);
1067
1068
1069
1070
			mysql_free_result(res);
			return 1;
		}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
1071
1072
		gidint = atoi(row[1]);
		sprintf(buf, "ADDGROUP NAME=%s GID=%d\n", row[0], gidint);
1073
		client_writeback(sock, buf, strlen(buf), tcp);
1074
		info("ACCOUNTS: %s", buf);
1075
1076
1077

		nrows--;
	}
1078
1079
	mysql_free_result(res);

1080
1081
	/*
	 * Each time a node picks up accounts, decrement the update
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1082
1083
1084
	 * counter. This ensures that if someone kicks off another
	 * update after this point, the node will end up getting to
	 * do it again in case it missed something.
1085
1086
1087
	 */
	if (mydb_update("update nodes set update_accounts=update_accounts-1 "
			"where node_id='%s' and update_accounts!=0",
1088
			reqp->nodeid)) {
1089
		error("ACCOUNTS: %s: DB Error setting exit update_accounts!\n",
1090
		      reqp->nodeid);
1091
1092
	}
			 
1093
1094
1095
	/*
	 * Now onto the users in the project.
	 */
1096
	if (reqp->islocal || reqp->isvnode) {
1097
1098
1099
1100
1101
1102
1103
1104
1105
		/*
		 * This crazy join is going to give us multiple lines for
		 * each user that is allowed on the node, where each line
		 * (for each user) differs by the project PID and it unix
		 * GID. The intent is to build up a list of GIDs for each
		 * user to return. Well, a primary group and a list of aux
		 * groups for that user. It might be cleaner to do this as
		 * multiple querys, but this makes it atomic.
		 */
1106
		if (strcmp(reqp->pid, reqp->gid)) {
1107
1108
1109
1110
1111
			res = mydb_query("select distinct "
			     "  u.uid,u.usr_pswd,u.unix_uid,u.usr_name, "
			     "  p.trust,g.pid,g.gid,g.unix_gid,u.admin, "
			     "  u.emulab_pubkey,u.home_pubkey, "
			     "  UNIX_TIMESTAMP(u.usr_modified) "
Robert Ricci's avatar
Robert Ricci committed
1112
1113
			     "from group_membership as p "
			     "left join users as u on p.uid=u.uid "
1114
1115
1116
1117
			     "left join groups as g on p.pid=g.pid "
			     "where ((p.pid='%s' and p.gid='%s')) "
			     "      and p.trust!='none' "
			     "      and u.status='active' order by u.uid",
1118
			     12, reqp->pid, reqp->gid);
1119
1120
1121
1122
1123
1124
1125
		}
		else {
			res = mydb_query("select distinct "
			     "  u.uid,u.usr_pswd,u.unix_uid,u.usr_name, "
			     "  p.trust,g.pid,g.gid,g.unix_gid,u.admin, "
			     "  u.emulab_pubkey,u.home_pubkey, "
			     "  UNIX_TIMESTAMP(u.usr_modified) "
Robert Ricci's avatar
Robert Ricci committed
1126
1127
			     "from group_membership as p "
			     "left join users as u on p.uid=u.uid "