tmcd.c 76.4 KB
Newer Older
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1
2
3
4
5
6
/*
 * EMULAB-COPYRIGHT
 * Copyright (c) 2000-2002 University of Utah and the Flux Group.
 * 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
#include <paths.h>
Austin Clements's avatar
Austin Clements committed
24
#include <setjmp.h>
25
26
#include <mysql/mysql.h>
#include "decls.h"
27
#include "config.h"
28
29
#include "ssl.h"
#include "log.h"
30
#include "tbdefs.h"
31

32
33
34
35
#ifdef EVENTSYS
#include "event.h"
#endif

36
37
38
/*
 * XXX This needs to be localized!
 */
39
40
41
#define FSPROJDIR	FSNODE ":" FSDIR_PROJ
#define FSGROUPDIR	FSNODE ":" FSDIR_GROUPS
#define FSUSERDIR	FSNODE ":" FSDIR_USERS
42
#define PROJDIR		"/proj"
43
#define GROUPDIR	"/groups"
44
45
46
#define USERDIR		"/users"
#define RELOADPID	"emulab-ops"
#define RELOADEID	"reloading"
Austin Clements's avatar
Austin Clements committed
47
48
#define FSHOSTID	"/usr/testbed/etc/fshostid"
//#define FSHOSTID	"./fshostid"
49

50
51
52
#define TESTMODE
#define NETMASK		"255.255.255.0"

53
54
/* Defined in configure and passed in via the makefile */
#define DBNAME_SIZE	64
Austin Clements's avatar
Austin Clements committed
55
#define HOSTID_SIZE	(32+64)
56
57
#define DEFAULT_DBNAME	TBDBNAME

58
int		debug = 0;
59
static int	insecure = 0;
60
61
static int	portnum = TBSERVER_PORT;
static char     dbname[DBNAME_SIZE];
62
static struct in_addr myipaddr;
Austin Clements's avatar
Austin Clements committed
63
static char	fshostid[HOSTID_SIZE];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
64
static int	nodeidtoexp(char *nodeid, char *pid, char *eid, char *gid);
65
static int	iptonodeid(struct in_addr, char *,char *,char *,char *,int *);
66
static int	nodeidtonickname(char *nodeid, char *nickname);
67
static int	nodeidtocontrolnet(char *nodeid, int *net);
68
static int	checkdbredirect(char *nodeid);
69
70
71
static void	tcpserver(int sock);
static void	udpserver(int sock);
static int      handle_request(int, struct sockaddr_in *, char *, int);
Mike Hibler's avatar
Mike Hibler committed
72
73
int		client_writeback(int sock, void *buf, int len, int tcp);
void		client_writeback_done(int sock, struct sockaddr_in *client);
74
75
76
MYSQL_RES *	mydb_query(char *query, int ncols, ...);
int		mydb_update(char *query, ...);

77
78
79
80
81
/* thread support */
#define MAXCHILDREN	25
#define MINCHILDREN	5
static int	udpchild;
static int	numchildren;
82
static int	maxchildren = 15;
83
static volatile int killme;
84

85
86
87
88
89
#ifdef EVENTSYS
int			myevent_send(address_tuple_t address);
static event_handle_t	event_handle = NULL;
#endif

90
91
92
/*
 * Commands we support.
 */
93
94
#define COMMAND_PROTOTYPE(x) \
	static int \
95
96
	x(int sock, char *nodeid, char *rdata, int tcp, \
	  int islocal, char *nodetype, int vers)
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113

COMMAND_PROTOTYPE(doreboot);
COMMAND_PROTOTYPE(dostatus);
COMMAND_PROTOTYPE(doifconfig);
COMMAND_PROTOTYPE(doaccounts);
COMMAND_PROTOTYPE(dodelay);
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
114
115
COMMAND_PROTOTYPE(dosfshostid);
//COMMAND_PROTOTYPE(dosfsmounts);
116
117
118
119
120
121
122
COMMAND_PROTOTYPE(doloadinfo);
COMMAND_PROTOTYPE(doreset);
COMMAND_PROTOTYPE(dorouting);
COMMAND_PROTOTYPE(dotrafgens);
COMMAND_PROTOTYPE(donseconfigs);
COMMAND_PROTOTYPE(dostate);
COMMAND_PROTOTYPE(docreator);
123
COMMAND_PROTOTYPE(dotunnels);
124
COMMAND_PROTOTYPE(dovnodelist);
125
COMMAND_PROTOTYPE(doisalive);
126
COMMAND_PROTOTYPE(doipodinfo);
127
128
129

struct command {
	char	*cmdname;
130
	int    (*func)(int, char *, char *, int, int, char *, int);
131
} command_array[] = {
132
133
134
135
136
	{ "reboot",	doreboot },
	{ "status",	dostatus },
	{ "ifconfig",	doifconfig },
	{ "accounts",	doaccounts },
	{ "delay",	dodelay },
137
138
	{ "hostnamesV2",dohostsV2 },	/* This will go away */
	{ "hostnames",	dohosts },
139
	{ "rpms",	dorpms },
140
	{ "deltas",	dodeltas },
141
	{ "tarballs",	dotarballs },
142
	{ "startupcmd",	dostartcmd },
Mike Hibler's avatar
Mike Hibler committed
143
	{ "startstatus",dostartstat }, /* Leave this before "startstat" */
144
	{ "startstat",	dostartstat },
145
146
	{ "readycount", doreadycount },
	{ "ready",	doready },
Mike Hibler's avatar
Mike Hibler committed
147
	{ "log",	dolog },
148
	{ "mounts",	domounts },
Austin Clements's avatar
Austin Clements committed
149
150
	{ "sfshostid",	dosfshostid },
//	{ "sfsmounts",	dosfsmounts },
151
	{ "loadinfo",	doloadinfo},
Robert Ricci's avatar
Robert Ricci committed
152
	{ "reset",	doreset},
153
	{ "routing",	dorouting},
154
	{ "trafgens",	dotrafgens},
155
	{ "nseconfigs",	donseconfigs},
156
	{ "creator",	docreator},
157
	{ "state",	dostate},
158
	{ "tunnels",	dotunnels},
159
	{ "vnodelist",	dovnodelist},
160
	{ "isalive",	doisalive},
161
	{ "ipodinfo",	doipodinfo},
162
163
164
};
static int numcommands = sizeof(command_array)/sizeof(struct command);

165
166
167
168
169
170
171
172
/* 
 * Simple struct used to make a linked list of ifaces
 */
struct node_interface {
	char *iface;
	struct node_interface *next;
};

173
int	directly_connected(struct node_interface *interfaces, char *iface);
174

175
176
177
178
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"
179
 " -c num	   Specify number of servers (must be %d <= x <= %d)\n"
180
181
182
183
184
 "\n";

void
usage()
{
185
	fprintf(stderr, usagestr, MINCHILDREN, MAXCHILDREN);
186
187
188
	exit(1);
}

189
190
191
192
193
static void
cleanup()
{
	signal(SIGHUP, SIG_IGN);
	killme = 1;
194
	killpg(0, SIGHUP);
195
196
}

Mike Hibler's avatar
Mike Hibler committed
197
198
int
main(int argc, char **argv)
199
{
200
	int			tcpsock, udpsock, ch;
201
	int			length, i, status, pid;
Mike Hibler's avatar
Mike Hibler committed
202
	struct sockaddr_in	name;
203
204
	FILE			*fp;
	char			buf[BUFSIZ];
205
	struct hostent		*he;
206
	extern char		build_info[];
207

208
	while ((ch = getopt(argc, argv, "dp:c:X")) != -1)
209
210
211
212
213
214
		switch(ch) {
		case 'p':
			portnum = atoi(optarg);
			break;
		case 'd':
			debug++;
215
			break;
216
217
218
		case 'c':
			maxchildren = atoi(optarg);
			break;
219
220
221
222
223
#ifdef LBS
		case 'X':
			insecure = 1;
			break;
#endif
224
225
226
227
228
229
230
231
232
233
		case 'h':
		case '?':
		default:
			usage();
		}
	argc -= optind;
	argv += optind;

	if (argc)
		usage();
234
235
	if (maxchildren < MINCHILDREN || maxchildren > MAXCHILDREN)
		usage();
236

237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
#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
252

Austin Clements's avatar
Austin Clements committed
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
	/*
	 * 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;
			}
			else {
				error("fshostid from %s may be corrupt: %s",
				      FSHOSTID, fshostid);
			}
			fclose(fp);
		}
	}
	
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
	/*
	 * 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);

Mike Hibler's avatar
Mike Hibler committed
291
	/*
292
	 * Setup TCP socket for incoming connections.
Mike Hibler's avatar
Mike Hibler committed
293
294
	 */

295
	/* Create socket from which to read. */
Mike Hibler's avatar
Mike Hibler committed
296
297
	tcpsock = socket(AF_INET, SOCK_STREAM, 0);
	if (tcpsock < 0) {
298
		pfatal("opening stream socket");
299
300
	}

301
	i = 1;
Mike Hibler's avatar
Mike Hibler committed
302
	if (setsockopt(tcpsock, SOL_SOCKET, SO_REUSEADDR,
303
		       (char *)&i, sizeof(i)) < 0)
304
		pwarning("setsockopt(SO_REUSEADDR)");;
305
306
307
308
	
	/* Create name. */
	name.sin_family = AF_INET;
	name.sin_addr.s_addr = INADDR_ANY;
309
	name.sin_port = htons((u_short) portnum);
Mike Hibler's avatar
Mike Hibler committed
310
	if (bind(tcpsock, (struct sockaddr *) &name, sizeof(name))) {
311
		pfatal("binding stream socket");
312
313
314
	}
	/* Find assigned port value and print it out. */
	length = sizeof(name);
Mike Hibler's avatar
Mike Hibler committed
315
	if (getsockname(tcpsock, (struct sockaddr *) &name, &length)) {
316
		pfatal("getsockname");
317
	}
Mike Hibler's avatar
Mike Hibler committed
318
	if (listen(tcpsock, 20) < 0) {
319
		pfatal("listen");
320
	}
321
322
	info("listening on TCP port %d\n", ntohs(name.sin_port));
	
Mike Hibler's avatar
Mike Hibler committed
323
324
325
326
327
328
329
	/*
	 * Setup UDP socket
	 */

	/* Create socket from which to read. */
	udpsock = socket(AF_INET, SOCK_DGRAM, 0);
	if (udpsock < 0) {
330
		pfatal("opening dgram socket");
Mike Hibler's avatar
Mike Hibler committed
331
332
333
334
335
	}

	i = 1;
	if (setsockopt(udpsock, SOL_SOCKET, SO_REUSEADDR,
		       (char *)&i, sizeof(i)) < 0)
336
		pwarning("setsockopt(SO_REUSEADDR)");;
Mike Hibler's avatar
Mike Hibler committed
337
338
339
340
	
	/* Create name. */
	name.sin_family = AF_INET;
	name.sin_addr.s_addr = INADDR_ANY;
341
	name.sin_port = htons((u_short) portnum);
Mike Hibler's avatar
Mike Hibler committed
342
	if (bind(udpsock, (struct sockaddr *) &name, sizeof(name))) {
343
		pfatal("binding dgram socket");
Mike Hibler's avatar
Mike Hibler committed
344
345
346
347
348
	}

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

353
354
355
356
	signal(SIGTERM, cleanup);
	signal(SIGINT, cleanup);
	signal(SIGHUP, cleanup);

357
358
359
360
361
362
363
364
365
366
	/*
	 * 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);
	}

367
368
369
370
	/*
	 * 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. 
	 */
371
	while (1) {
372
		while (!killme && numchildren < maxchildren) {
373
374
			int doudp = (udpchild ? 0 : 1);
			if ((pid = fork()) < 0) {
375
				errorc("forking server");
376
				goto done;
Mike Hibler's avatar
Mike Hibler committed
377
			}
378
379
380
381
			if (pid) {
				if (doudp)
					udpchild = pid;
				numchildren++;
Mike Hibler's avatar
Mike Hibler committed
382
383
				continue;
			}
384
			/* Child does useful work! Never Returns! */
385
386
387
388
			signal(SIGTERM, SIG_DFL);
			signal(SIGINT, SIG_DFL);
			signal(SIGHUP, SIG_DFL);
			
389
390
391
392
393
			if (doudp) 
				udpserver(udpsock);
			else
				tcpserver(tcpsock);
			exit(-1);
394
		}
Mike Hibler's avatar
Mike Hibler committed
395

396
		/*
397
		 * Parent waits.
398
		 */
399
400
		pid = waitpid(-1, &status, 0);
		if (pid < 0) {
401
			errorc("waitpid failed");
402
			continue;
403
		}
404
		error("server %d exited with status 0x%x!\n", pid, status);
405
406
407
		numchildren--;
		if (pid == udpchild)
			udpchild = 0;
408
409
		if (killme && !numchildren)
			break;
410
411
	}
 done:
412
	CLOSE(tcpsock);
413
	close(udpsock);
414
	info("daemon terminating\n");
415
416
	exit(0);
}
417

418
/*
419
 * Listen for UDP requests. This is not a secure channel, and so this should
420
421
422
423
424
425
426
427
428
 * eventually be killed off.
 */
static void
udpserver(int sock)
{
	char			buf[MYBUFSIZE];
	struct sockaddr_in	client;
	int			length, cc;
	
429
	info("udpserver starting\n");
430
431
432
433
434
435
436
437
438
439

	/*
	 * Wait for udp connections.
	 */
	while (1) {
		length = sizeof(client);		
		cc = recvfrom(sock, buf, MYBUFSIZE - 1,
			      0, (struct sockaddr *)&client, &length);
		if (cc <= 0) {
			if (cc < 0)
440
441
				errorc("Reading UDP request");
			error("UDP Connection aborted\n");
442
			continue;
443
		}
444
445
446
447
448
449
450
		buf[cc] = '\0';
		handle_request(sock, &client, buf, 0);
	}
	exit(1);
}

/*
451
 * Listen for TCP requests.
452
453
454
455
456
457
458
459
 */
static void
tcpserver(int sock)
{
	char			buf[MYBUFSIZE];
	struct sockaddr_in	client;
	int			length, cc, newsock;
	
460
	info("tcpserver starting\n");
461
462
463
464
465
466

	/*
	 * Wait for TCP connections.
	 */
	while (1) {
		length  = sizeof(client);
467
		newsock = ACCEPT(sock, (struct sockaddr *)&client, &length);
468
		if (newsock < 0) {
469
			errorc("accepting TCP connection");
470
			continue;
471
		}
Mike Hibler's avatar
Mike Hibler committed
472
473

		/*
474
		 * Read in the command request.
Mike Hibler's avatar
Mike Hibler committed
475
		 */
476
		if ((cc = READ(newsock, buf, MYBUFSIZE - 1)) <= 0) {
477
			if (cc < 0)
478
479
480
				errorc("Reading TCP request");
			error("TCP connection aborted\n");
			CLOSE(newsock);
481
			continue;
482
		}
483
484
		buf[cc] = '\0';
		handle_request(newsock, &client, buf, 1);
485
		CLOSE(newsock);
486
487
488
	}
	exit(1);
}
Mike Hibler's avatar
Mike Hibler committed
489

490
491
492
493
static int
handle_request(int sock, struct sockaddr_in *client, char *rdata, int istcp)
{
	struct sockaddr_in redirect_client;
494
	int		   redirect = 0, isvnode = 0;
495
	char		   buf[BUFSIZ], *bp, *cp;
496
	char		   nodeid[TBDB_FLEN_NODEID];
497
	char		   vnodeid[TBDB_FLEN_NODEID];
498
499
	char		   class[TBDB_FLEN_NODECLASS];
	char		   type[TBDB_FLEN_NODETYPE];
500
	int		   i, islocal, err = 0;
501
	int		   version = DEFAULT_VERSION;
Mike Hibler's avatar
Mike Hibler committed
502

503
504
505
	/*
	 * Look for special tags. 
	 */
506
	bp = rdata;
507
508
509
510
511
512
513
514
515
516
517
518
519
	while ((bp = strsep(&rdata, " ")) != NULL) {
		/*
		 * Look for VERSION. 
		 */
		if (sscanf(bp, "VERSION=%d", &i) == 1) {
			version = i;
			
			if (debug) {
				info("VERSION %d\n", version);
			}
			
			continue;
		}
Mike Hibler's avatar
Mike Hibler committed
520

521
522
523
524
525
526
527
528
529
530
		/*
		 * 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);
531

532
533
			info("REDIRECTED from %s to %s\n",
			     inet_ntoa(redirect_client.sin_addr), buf);
534

535
536
537
538
539
540
541
542
543
544
545
546
547
			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)) {
			isvnode = 1;
			strncpy(vnodeid, buf, sizeof(vnodeid));
548

549
550
551
552
553
			if (debug) {
				info("VNODEID %s\n", buf);
			}
			continue;
		}
554

555
556
557
558
559
		/*
		 * 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
560
561
562
		 *
		 * Note that rdata will point to any text after the command.
		 *
563
564
565
566
567
		 */
		if (*bp) {
			break;
		}
	}
568

569
570
571
	/* Start with default DB */
	strcpy(dbname, DEFAULT_DBNAME);

572
	/*
573
	 * Map the ip to a nodeid.
574
	 */
575
576
577
578
579
580
581
582
583
584
	if ((err = iptonodeid(client->sin_addr, (isvnode ? vnodeid : NULL),
			      nodeid, class, type, &islocal))) {
		if (err == 2) {
			error("No such node vnode mapping %s on %s\n",
			      vnodeid, nodeid);
		}
		else {
			error("No such node: %s\n",
			      inet_ntoa(client->sin_addr));
		}
585
586
587
588
589
590
591
592
		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. 
	 */
593
	if (!insecure && redirect &&
594
	    redirect_client.sin_addr.s_addr != myipaddr.s_addr) {
595
596
597
598
599
		char	buf1[32], buf2[32];
		
		strcpy(buf1, inet_ntoa(redirect_client.sin_addr));
		strcpy(buf2, inet_ntoa(client->sin_addr));
			
600
		info("%s INVALID REDIRECT: %s\n", buf1, buf2);
601
602
		goto skipit;
	}
603
604
605
606
607
608
609
610
611
612
613
614
615

#ifdef  WITHSSL
	/*
	 * If the connection is not SSL, then it must be a local node.
	 */
	if (isssl) {
		if (tmcd_sslverify_client(nodeid, class, type, islocal)) {
			error("%s: SSL verification failure\n", nodeid);
			if (! redirect)
				goto skipit;
		}
	}
	else if (!islocal) {
616
617
618
619
		if (!istcp) {
			/*
			 * Simple "isalive" support for remote nodes.
			 */
620
621
			doisalive(sock, nodeid, rdata, istcp,
				  islocal, type, version);
622
623
			goto skipit;
		}
624
		error("%s: Remote node connected without SSL!\n", nodeid);
625
626
		if (!insecure)
			goto skipit;
627
628
629
630
631
632
	}
#else
	/*
	 * When not compiled for ssl, do not allow remote connections.
	 */
	if (!islocal) {
633
634
635
636
		if (!istcp) {
			/*
			 * Simple "isup" daemon support!
			 */
637
638
			doisalive(sock, nodeid, rdata, istcp,
				  islocal, type, version);
639
640
641
			goto skipit;
		}
		error("%s: Remote node connected without SSL!\n", nodeid);
642
643
		if (!insecure)
			goto skipit;
644
	}
645
646
647
648
#endif
	/*
	 * Check for a redirect using the default DB. This allows
	 * for a simple redirect to a secondary DB for testing.
649
	 * Upon return, the dbname has been changed if redirected.
650
	 */
651
	if (checkdbredirect(nodeid)) {
652
653
654
		/* Something went wrong */
		goto skipit;
	}
655

656
657
658
659
	/*
	 * Figure out what command was given.
	 */
	for (i = 0; i < numcommands; i++)
660
		if (strncmp(bp, command_array[i].cmdname,
661
662
			    strlen(command_array[i].cmdname)) == 0)
			break;
Mike Hibler's avatar
Mike Hibler committed
663

664
	if (i == numcommands) {
665
		info("%s: INVALID REQUEST: %.8s\n", nodeid, bp);
666
667
		goto skipit;
	}
668

669
670
671
	/*
	 * Execute it.
	 */
672
673
674
675
676
#ifdef	WITHSSL
	cp = isssl ? "ssl:yes" : "ssl:no";
#else
	cp = "";
#endif
677
678
679
680
681
	/*
	 * XXX hack, don't log "log" contents,
	 * both for privacy and to keep our syslog smaller.
	 */
	if (command_array[i].func == dolog)
682
683
		info("%s: vers:%d %s %s log %d chars\n", nodeid, version,
		     istcp ? "TCP" : "UDP", cp, strlen(rdata));
684
	else
685
686
		info("%s: vers:%d %s %s %s\n", nodeid, version,
		     istcp ? "TCP" : "UDP", cp, command_array[i].cmdname);
687

688
689
	err = command_array[i].func(sock, nodeid, rdata, istcp,
				    islocal, type, version);
690
691
692
693

	if (err)
		info("%s: %s: returned %d\n",
		     nodeid, command_array[i].cmdname, err);
694

695
 skipit:
696
697
698
	if (!istcp) 
		client_writeback_done(sock,
				      redirect ? &redirect_client : client);
699
	return 0;
700
701
702
703
704
}

/*
 * Accept notification of reboot. 
 */
705
COMMAND_PROTOTYPE(doreboot)
706
707
{
	MYSQL_RES	*res;	
708
709
	char		pid[64];
	char		eid[64];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
710
	char		gid[64];
711

712
713
714
715
716
717
718
719
	/*
	 * Clear the current_reloads for this node, in case it just finished
	 * reloading. This needs to happen regardless of whether or not the
	 * node is free or in the reloading experiment.
	 * XXX: Is it better to blindly do the delete (which will be harmless
	 * if there is no entry for this node) or to check first, which
	 * might waste time?
	 */
720
	info("doreload: %s: Clearing current_reloads\n", nodeid);
721
722
	if (mydb_update("delete from current_reloads where node_id='%s'",
		        nodeid)) {
723
724
	    error("doreload: %s: DB Error clearing current_reloads!\n",
		  nodeid);
725
726
727
	    return 1;
	}

728
729
730
731
732
733
	/*
	 * Need to check the pid/eid to distinguish between a user
	 * initiated reload, and a admin scheduled reload. We don't want
	 * to deschedule an admin reload, which is supposed to happen when
	 * the node is released.
	 */
Leigh B. Stoller's avatar
Leigh B. Stoller committed
734
	if (nodeidtoexp(nodeid, pid, eid, gid)) {
735
		info("REBOOT: %s: Node is free\n", nodeid);
736
737
738
		return 0;
	}

739
	info("REBOOT: %s: Node is in experiment %s/%s\n", nodeid, pid, eid);
740
741
742
743
744

	/*
	 * XXX This must match the reservation made in sched_reload
	 *     in the tbsetup directory.
	 */
745
746
	if (strcmp(pid, RELOADPID) ||
	    strcmp(eid, RELOADEID)) {
747
748
749
		return 0;
	}

750
751
752
753
	/*
	 * See if the node was in the reload state. If so we need to clear it
	 * and its reserved status.
	 */
754
755
	res = mydb_query("select node_id from scheduled_reloads "
			 "where node_id='%s'",
756
757
			 1, nodeid);
	if (!res) {
758
		error("REBOOT: %s: DB Error getting reload!\n", nodeid);
759
760
761
762
763
764
765
766
		return 1;
	}
	if ((int)mysql_num_rows(res) == 0) {
		mysql_free_result(res);
		return 0;
	}
	mysql_free_result(res);

767
768
	if (mydb_update("delete from scheduled_reloads where node_id='%s'",
			nodeid)) {
769
		error("REBOOT: %s: DB Error clearing reload!\n", nodeid);
770
771
		return 1;
	}
772
	info("REBOOT: %s cleared reload state\n", nodeid);
773
774

	if (mydb_update("delete from reserved where node_id='%s'", nodeid)) {
775
		error("REBOOT: %s: DB Error clearing reload!\n", nodeid);
776
777
		return 1;
	}
778
	info("REBOOT: %s cleared reserved state\n", nodeid);
779
780
781
782
783
784
785

	return 0;
}

/*
 * Return status of node. Is it allocated to an experiment, or free.
 */
786
COMMAND_PROTOTYPE(dostatus)
787
788
789
{
	char		pid[64];
	char		eid[64];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
790
	char		gid[64];
791
	char		nickname[128];
Mike Hibler's avatar
Mike Hibler committed
792
	char		buf[MYBUFSIZE];
793
794
795
796

	/*
	 * Now check reserved table
	 */
Leigh B. Stoller's avatar
Leigh B. Stoller committed
797
	if (nodeidtoexp(nodeid, pid, eid, gid)) {
798
		info("STATUS: %s: Node is free\n", nodeid);
799
800
801
		strcpy(buf, "FREE\n");
		client_writeback(sock, buf, strlen(buf), tcp);
		return 0;
802
803
	}

804
805
806
807
808
809
810
811
812
	/*
	 * Need the nickname too.
	 */
	if (nodeidtonickname(nodeid, nickname))
		strcpy(nickname, nodeid);

	sprintf(buf, "ALLOCATED=%s/%s NICKNAME=%s\n", pid, eid, nickname);
	client_writeback(sock, buf, strlen(buf), tcp);

813
	info("STATUS: %s: %s", nodeid, buf);
814
815
816
817
818
819
	return 0;
}

/*
 * Return ifconfig information to client.
 */
820
COMMAND_PROTOTYPE(doifconfig)
821
822
823
824
825
{
	MYSQL_RES	*res;	
	MYSQL_ROW	row;
	char		pid[64];
	char		eid[64];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
826
	char		gid[64];
Mike Hibler's avatar
Mike Hibler committed
827
	char		buf[MYBUFSIZE];
828
829
830
831
832
	int		control_net, nrows;

	/*
	 * Now check reserved table
	 */
Leigh B. Stoller's avatar
Leigh B. Stoller committed
833
	if (nodeidtoexp(nodeid, pid, eid, gid)) {
834
		info("IFCONFIG: %s: Node is free\n", nodeid);
835
836
837
838
839
840
841
842
		return 1;
	}

	/*
	 * Need to know the control network for the machine since
	 * we don't want to mess with that.
	 */
	if (nodeidtocontrolnet(nodeid, &control_net)) {
843
		error("IFCONFIG: %s: No Control Network\n", nodeid);
844
845
846
847
848
849
		return 1;
	}

	/*
	 * Find all the interfaces.
	 */
850
851
852
	res = mydb_query("select card,IP,IPalias,MAC,current_speed,duplex "
			 "from interfaces where node_id='%s'",
			 6, nodeid);
853
	if (!res) {
854
		error("IFCONFIG: %s: DB Error getting interfaces!\n", nodeid);
855
856
857
858
		return 1;
	}

	if ((nrows = (int)mysql_num_rows(res)) == 0) {
859
		error("IFCONFIG: %s: No interfaces!\n", nodeid);
860
861
862
863
864
865
		mysql_free_result(res);
		return 1;
	}
	while (nrows) {
		row = mysql_fetch_row(res);
		if (row[1] && row[1][0]) {
866
867
868
869
			int card    = atoi(row[0]);
			char *speed  = "100";
			char *unit   = "Mbps";
			char *duplex = "full";
870

871
872
873
874
875
876
			/*
			 * 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.
			 */
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
			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;

897
898
899
900
901
902
903
			/*
			 * Tack on MAC, which should go up above after
			 * Sharks are sunk.
			 */
			strcat(buf, " MAC=");
			strcat(buf, row[3]);

904
905
906
907
908
909
910
911
912
913
914
915
916
			/*
			 * 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);

917
			strcat(buf, "\n");
Mike Hibler's avatar
Mike Hibler committed
918
			client_writeback(sock, buf, strlen(buf), tcp);
919
			info("IFCONFIG: %s", buf);
920
		}
921
	skipit:
922
923
924
925
926
927
928
929
930
931
		nrows--;
	}
	mysql_free_result(res);

	return 0;
}

/*
 * Return account stuff.
 */
932
COMMAND_PROTOTYPE(doaccounts)
933
934
935
936
937
{
	MYSQL_RES	*res;	
	MYSQL_ROW	row;
	char		pid[64];
	char		eid[64];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
938
	char		gid[64];
Mike Hibler's avatar
Mike Hibler committed
939
	char		buf[MYBUFSIZE];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
940
	int		nrows, gidint;
941
	int		tbadmin;
942

943
944
945
	if (! tcp) {
		error("ACCOUNTS: %s: Cannot give account info out over UDP!\n",
		      nodeid);
946
947
948
949
950
951
		return 1;
	}

	/*
	 * Now check reserved table
	 */
952
	if (islocal && nodeidtoexp(nodeid, pid, eid, gid)) {
953
		error("ACCOUNTS: %s: Node is free\n", nodeid);
954
955
956
		return 1;
	}

957
958
        /*
	 * We need the unix GID and unix name for each group in the project.
959
	 */
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
	if (islocal) {
		res = mydb_query("select unix_name,unix_gid from groups "
				 "where pid='%s'",
				 2, pid);
	}
	else {
		/*
		 * XXX
		 * 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 "
978
979
				 "where p.approved!=0 and "
				 "      FIND_IN_SET('%s',pcremote_ok)>0",
980
981
				 2, nodetype);
	}
982
	if (!res) {
983
		error("ACCOUNTS: %s: DB Error getting gids!\n", pid);
984
985
986
987
		return 1;
	}

	if ((nrows = (int)mysql_num_rows(res)) == 0) {
988
		error("ACCOUNTS: %s: No Project!\n", pid);
989
990
991
992
		mysql_free_result(res);
		return 1;
	}

993
994
995
	while (nrows) {
		row = mysql_fetch_row(res);
		if (!row[1] || !row[1][1]) {
996
			error("ACCOUNTS: %s: No Project GID!\n", pid);
997
998
999
1000
			mysql_free_result(res);
			return 1;
		}