tmcd.c 68.7 KB
Newer Older
1
2
3
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
Mike Hibler's avatar
Mike Hibler committed
4
#include <arpa/inet.h>
5
#include <netdb.h>
Mike Hibler's avatar
Mike Hibler committed
6
#include <ctype.h>
7
#include <stdio.h>
Mike Hibler's avatar
Mike Hibler committed
8
9
10
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
11
12
13
14
#include <syslog.h>
#include <signal.h>
#include <stdarg.h>
#include <assert.h>
15
#include <sys/wait.h>
16
#include <paths.h>
17
18
#include <mysql/mysql.h>
#include "decls.h"
19
#include "config.h"
20
21
#include "ssl.h"
#include "log.h"
22
#include "tbdefs.h"
23

24
25
26
27
#ifdef EVENTSYS
#include "event.h"
#endif

28
29
30
/*
 * XXX This needs to be localized!
 */
31
32
33
#define FSPROJDIR	FSNODE ":" FSDIR_PROJ
#define FSGROUPDIR	FSNODE ":" FSDIR_GROUPS
#define FSUSERDIR	FSNODE ":" FSDIR_USERS
34
#define PROJDIR		"/proj"
35
#define GROUPDIR	"/groups"
36
37
38
39
#define USERDIR		"/users"
#define RELOADPID	"emulab-ops"
#define RELOADEID	"reloading"

40
41
42
#define TESTMODE
#define NETMASK		"255.255.255.0"

43
44
45
46
/* Defined in configure and passed in via the makefile */
#define DBNAME_SIZE	64
#define DEFAULT_DBNAME	TBDBNAME

47
int		debug = 0;
48
49
static int	portnum = TBSERVER_PORT;
static char     dbname[DBNAME_SIZE];
50
static struct in_addr myipaddr;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
51
static int	nodeidtoexp(char *nodeid, char *pid, char *eid, char *gid);
52
static int	iptonodeid(struct in_addr, char *,char *,char *,char *,int *);
53
static int	nodeidtonickname(char *nodeid, char *nickname);
54
static int	nodeidtocontrolnet(char *nodeid, int *net);
55
static int	checkdbredirect(char *nodeid);
56
57
58
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
59
60
int		client_writeback(int sock, void *buf, int len, int tcp);
void		client_writeback_done(int sock, struct sockaddr_in *client);
61
62
63
MYSQL_RES *	mydb_query(char *query, int ncols, ...);
int		mydb_update(char *query, ...);

64
65
66
67
68
/* thread support */
#define MAXCHILDREN	25
#define MINCHILDREN	5
static int	udpchild;
static int	numchildren;
69
static int	maxchildren = 15;
70
static volatile int killme;
71

72
73
74
75
76
#ifdef EVENTSYS
int			myevent_send(address_tuple_t address);
static event_handle_t	event_handle = NULL;
#endif

77
78
79
/*
 * Commands we support.
 */
80
81
#define COMMAND_PROTOTYPE(x) \
	static int \
82
	x(int sock, char *nodeid, char *rdata, int tcp, int vers)
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106

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);
COMMAND_PROTOTYPE(doloadinfo);
COMMAND_PROTOTYPE(doreset);
COMMAND_PROTOTYPE(dorouting);
COMMAND_PROTOTYPE(dotrafgens);
COMMAND_PROTOTYPE(donseconfigs);
COMMAND_PROTOTYPE(dostate);
COMMAND_PROTOTYPE(docreator);
107
COMMAND_PROTOTYPE(dotunnels);
108
COMMAND_PROTOTYPE(dovnodelist);
109
COMMAND_PROTOTYPE(doisalive);
110
111
112

struct command {
	char	*cmdname;
113
	int    (*func)(int, char *, char *, int, int);
114
} command_array[] = {
115
116
117
118
119
	{ "reboot",	doreboot },
	{ "status",	dostatus },
	{ "ifconfig",	doifconfig },
	{ "accounts",	doaccounts },
	{ "delay",	dodelay },
120
121
	{ "hostnamesV2",dohostsV2 },	/* This will go away */
	{ "hostnames",	dohosts },
122
	{ "rpms",	dorpms },
123
	{ "deltas",	dodeltas },
124
	{ "tarballs",	dotarballs },
125
	{ "startupcmd",	dostartcmd },
Mike Hibler's avatar
Mike Hibler committed
126
	{ "startstatus",dostartstat }, /* Leave this before "startstat" */
127
	{ "startstat",	dostartstat },
128
129
	{ "readycount", doreadycount },
	{ "ready",	doready },
Mike Hibler's avatar
Mike Hibler committed
130
	{ "log",	dolog },
131
	{ "mounts",	domounts },
132
	{ "loadinfo",	doloadinfo},
Robert Ricci's avatar
Robert Ricci committed
133
	{ "reset",	doreset},
134
	{ "routing",	dorouting},
135
	{ "trafgens",	dotrafgens},
136
	{ "nseconfigs",	donseconfigs},
137
	{ "creator",	docreator},
138
	{ "state",	dostate},
139
	{ "tunnels",	dotunnels},
140
	{ "vnodelist",	dovnodelist},
141
	{ "isalive",	doisalive},
142
143
144
};
static int numcommands = sizeof(command_array)/sizeof(struct command);

145
146
147
148
149
150
151
152
/* 
 * Simple struct used to make a linked list of ifaces
 */
struct node_interface {
	char *iface;
	struct node_interface *next;
};

153
int	directly_connected(struct node_interface *interfaces, char *iface);
154

155
156
157
158
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"
159
 " -c num	   Specify number of servers (must be %d <= x <= %d)\n"
160
161
162
163
164
 "\n";

void
usage()
{
165
	fprintf(stderr, usagestr, MINCHILDREN, MAXCHILDREN);
166
167
168
	exit(1);
}

169
170
171
172
173
static void
cleanup()
{
	signal(SIGHUP, SIG_IGN);
	killme = 1;
174
	killpg(0, SIGHUP);
175
176
}

Mike Hibler's avatar
Mike Hibler committed
177
178
int
main(int argc, char **argv)
179
{
180
	int			tcpsock, udpsock, ch;
181
	int			length, i, status, pid;
Mike Hibler's avatar
Mike Hibler committed
182
	struct sockaddr_in	name;
183
184
	FILE			*fp;
	char			buf[BUFSIZ];
185
	struct hostent		*he;
186
	extern char		build_info[];
187

188
	while ((ch = getopt(argc, argv, "dp:c:")) != -1)
189
190
191
192
193
194
		switch(ch) {
		case 'p':
			portnum = atoi(optarg);
			break;
		case 'd':
			debug++;
195
			break;
196
197
198
		case 'c':
			maxchildren = atoi(optarg);
			break;
199
200
201
202
203
204
205
206
207
208
		case 'h':
		case '?':
		default:
			usage();
		}
	argc -= optind;
	argv += optind;

	if (argc)
		usage();
209
210
	if (maxchildren < MINCHILDREN || maxchildren > MAXCHILDREN)
		usage();
211

212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
#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
227

228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
	/*
	 * 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
243
	/*
244
	 * Setup TCP socket for incoming connections.
Mike Hibler's avatar
Mike Hibler committed
245
246
	 */

247
	/* Create socket from which to read. */
Mike Hibler's avatar
Mike Hibler committed
248
249
	tcpsock = socket(AF_INET, SOCK_STREAM, 0);
	if (tcpsock < 0) {
250
		pfatal("opening stream socket");
251
252
	}

253
	i = 1;
Mike Hibler's avatar
Mike Hibler committed
254
	if (setsockopt(tcpsock, SOL_SOCKET, SO_REUSEADDR,
255
		       (char *)&i, sizeof(i)) < 0)
256
		pwarning("setsockopt(SO_REUSEADDR)");;
257
258
259
260
	
	/* Create name. */
	name.sin_family = AF_INET;
	name.sin_addr.s_addr = INADDR_ANY;
261
	name.sin_port = htons((u_short) portnum);
Mike Hibler's avatar
Mike Hibler committed
262
	if (bind(tcpsock, (struct sockaddr *) &name, sizeof(name))) {
263
		pfatal("binding stream socket");
264
265
266
	}
	/* Find assigned port value and print it out. */
	length = sizeof(name);
Mike Hibler's avatar
Mike Hibler committed
267
	if (getsockname(tcpsock, (struct sockaddr *) &name, &length)) {
268
		pfatal("getsockname");
269
	}
Mike Hibler's avatar
Mike Hibler committed
270
	if (listen(tcpsock, 20) < 0) {
271
		pfatal("listen");
272
	}
273
274
	info("listening on TCP port %d\n", ntohs(name.sin_port));
	
Mike Hibler's avatar
Mike Hibler committed
275
276
277
278
279
280
281
	/*
	 * Setup UDP socket
	 */

	/* Create socket from which to read. */
	udpsock = socket(AF_INET, SOCK_DGRAM, 0);
	if (udpsock < 0) {
282
		pfatal("opening dgram socket");
Mike Hibler's avatar
Mike Hibler committed
283
284
285
286
287
	}

	i = 1;
	if (setsockopt(udpsock, SOL_SOCKET, SO_REUSEADDR,
		       (char *)&i, sizeof(i)) < 0)
288
		pwarning("setsockopt(SO_REUSEADDR)");;
Mike Hibler's avatar
Mike Hibler committed
289
290
291
292
	
	/* Create name. */
	name.sin_family = AF_INET;
	name.sin_addr.s_addr = INADDR_ANY;
293
	name.sin_port = htons((u_short) portnum);
Mike Hibler's avatar
Mike Hibler committed
294
	if (bind(udpsock, (struct sockaddr *) &name, sizeof(name))) {
295
		pfatal("binding dgram socket");
Mike Hibler's avatar
Mike Hibler committed
296
297
298
299
300
	}

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

305
306
307
308
	signal(SIGTERM, cleanup);
	signal(SIGINT, cleanup);
	signal(SIGHUP, cleanup);

309
310
311
312
313
314
315
316
317
318
	/*
	 * 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);
	}

319
320
321
322
	/*
	 * 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. 
	 */
323
	while (1) {
324
		while (!killme && numchildren < maxchildren) {
325
326
			int doudp = (udpchild ? 0 : 1);
			if ((pid = fork()) < 0) {
327
				errorc("forking server");
328
				goto done;
Mike Hibler's avatar
Mike Hibler committed
329
			}
330
331
332
333
			if (pid) {
				if (doudp)
					udpchild = pid;
				numchildren++;
Mike Hibler's avatar
Mike Hibler committed
334
335
				continue;
			}
336
			/* Child does useful work! Never Returns! */
337
338
339
340
			signal(SIGTERM, SIG_DFL);
			signal(SIGINT, SIG_DFL);
			signal(SIGHUP, SIG_DFL);
			
341
342
343
344
345
			if (doudp) 
				udpserver(udpsock);
			else
				tcpserver(tcpsock);
			exit(-1);
346
		}
Mike Hibler's avatar
Mike Hibler committed
347

348
		/*
349
		 * Parent waits.
350
		 */
351
352
		pid = waitpid(-1, &status, 0);
		if (pid < 0) {
353
			errorc("waitpid failed");
354
			continue;
355
		}
356
		error("server %d exited with status 0x%x!\n", pid, status);
357
358
359
		numchildren--;
		if (pid == udpchild)
			udpchild = 0;
360
361
		if (killme && !numchildren)
			break;
362
363
	}
 done:
364
	CLOSE(tcpsock);
365
	close(udpsock);
366
	info("daemon terminating\n");
367
368
	exit(0);
}
369

370
/*
371
 * Listen for UDP requests. This is not a secure channel, and so this should
372
373
374
375
376
377
378
379
380
 * eventually be killed off.
 */
static void
udpserver(int sock)
{
	char			buf[MYBUFSIZE];
	struct sockaddr_in	client;
	int			length, cc;
	
381
	info("udpserver starting\n");
382
383
384
385
386
387
388
389
390
391

	/*
	 * 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)
392
393
				errorc("Reading UDP request");
			error("UDP Connection aborted\n");
394
			continue;
395
		}
396
397
398
399
400
401
402
		buf[cc] = '\0';
		handle_request(sock, &client, buf, 0);
	}
	exit(1);
}

/*
403
 * Listen for TCP requests.
404
405
406
407
408
409
410
411
 */
static void
tcpserver(int sock)
{
	char			buf[MYBUFSIZE];
	struct sockaddr_in	client;
	int			length, cc, newsock;
	
412
	info("tcpserver starting\n");
413
414
415
416
417
418

	/*
	 * Wait for TCP connections.
	 */
	while (1) {
		length  = sizeof(client);
419
		newsock = ACCEPT(sock, (struct sockaddr *)&client, &length);
420
		if (newsock < 0) {
421
			errorc("accepting TCP connection");
422
			continue;
423
		}
Mike Hibler's avatar
Mike Hibler committed
424
425

		/*
426
		 * Read in the command request.
Mike Hibler's avatar
Mike Hibler committed
427
		 */
428
		if ((cc = READ(newsock, buf, MYBUFSIZE - 1)) <= 0) {
429
			if (cc < 0)
430
431
432
				errorc("Reading TCP request");
			error("TCP connection aborted\n");
			CLOSE(newsock);
433
			continue;
434
		}
435
436
		buf[cc] = '\0';
		handle_request(newsock, &client, buf, 1);
437
		CLOSE(newsock);
438
439
440
	}
	exit(1);
}
Mike Hibler's avatar
Mike Hibler committed
441

442
443
444
445
static int
handle_request(int sock, struct sockaddr_in *client, char *rdata, int istcp)
{
	struct sockaddr_in redirect_client;
446
	int		   redirect = 0, isvnode = 0;
447
	char		   buf[BUFSIZ], *bp, *cp;
448
	char		   nodeid[TBDB_FLEN_NODEID];
449
	char		   vnodeid[TBDB_FLEN_NODEID];
450
451
	char		   class[TBDB_FLEN_NODECLASS];
	char		   type[TBDB_FLEN_NODETYPE];
452
	int		   i, islocal, err = 0;
453
	int		   version = DEFAULT_VERSION;
Mike Hibler's avatar
Mike Hibler committed
454

455
456
457
	/*
	 * Look for special tags. 
	 */
458
	bp = rdata;
459
460
461
462
463
464
465
466
467
468
469
470
471
	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
472

473
474
475
476
477
478
479
480
481
482
		/*
		 * 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);
483

484
485
			info("REDIRECTED from %s to %s\n",
			     inet_ntoa(redirect_client.sin_addr), buf);
486

487
488
489
490
491
492
493
494
495
496
497
498
499
			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));
500

501
502
503
504
505
			if (debug) {
				info("VNODEID %s\n", buf);
			}
			continue;
		}
506

507
508
509
510
511
		/*
		 * 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
512
513
514
		 *
		 * Note that rdata will point to any text after the command.
		 *
515
516
517
518
519
		 */
		if (*bp) {
			break;
		}
	}
520

521
522
523
	/* Start with default DB */
	strcpy(dbname, DEFAULT_DBNAME);

524
	/*
525
	 * Map the ip to a nodeid.
526
	 */
527
528
529
530
531
532
533
534
535
536
	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));
		}
537
538
539
540
541
542
543
544
545
546
		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. 
	 */
	if (redirect &&
	    redirect_client.sin_addr.s_addr != myipaddr.s_addr) {
547
548
549
550
551
		char	buf1[32], buf2[32];
		
		strcpy(buf1, inet_ntoa(redirect_client.sin_addr));
		strcpy(buf2, inet_ntoa(client->sin_addr));
			
552
		info("%s INVALID REDIRECT: %s\n", buf1, buf2);
553
554
		goto skipit;
	}
555
556
557
558
559
560
561
562
563
564
565
566
567

#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) {
568
569
570
571
		if (!istcp) {
			/*
			 * Simple "isalive" support for remote nodes.
			 */
572
			doisalive(sock, nodeid, rdata, istcp, version);
573
574
			goto skipit;
		}
575
		error("%s: Remote node connected without SSL!\n", nodeid);
576
		goto skipit;
577
578
579
580
581
582
	}
#else
	/*
	 * When not compiled for ssl, do not allow remote connections.
	 */
	if (!islocal) {
583
584
585
586
		if (!istcp) {
			/*
			 * Simple "isup" daemon support!
			 */
587
			doisalive(sock, nodeid, rdata, istcp, version);
588
589
590
591
			goto skipit;
		}
		error("%s: Remote node connected without SSL!\n", nodeid);
		goto skipit;
592
	}
593
594
595
596
#endif
	/*
	 * Check for a redirect using the default DB. This allows
	 * for a simple redirect to a secondary DB for testing.
597
	 * Upon return, the dbname has been changed if redirected.
598
	 */
599
	if (checkdbredirect(nodeid)) {
600
601
602
		/* Something went wrong */
		goto skipit;
	}
603

604
605
606
607
	/*
	 * Figure out what command was given.
	 */
	for (i = 0; i < numcommands; i++)
608
		if (strncmp(bp, command_array[i].cmdname,
609
610
			    strlen(command_array[i].cmdname)) == 0)
			break;
Mike Hibler's avatar
Mike Hibler committed
611

612
	if (i == numcommands) {
613
		info("%s: INVALID REQUEST: %.8s\n", nodeid, bp);
614
615
		goto skipit;
	}
616

617
618
619
	/*
	 * Execute it.
	 */
620
621
622
623
624
#ifdef	WITHSSL
	cp = isssl ? "ssl:yes" : "ssl:no";
#else
	cp = "";
#endif
625
626
627
628
629
	/*
	 * XXX hack, don't log "log" contents,
	 * both for privacy and to keep our syslog smaller.
	 */
	if (command_array[i].func == dolog)
Leigh B. Stoller's avatar
Leigh B. Stoller committed
630
		info("%s: %s log %d chars\n", nodeid, cp, strlen(rdata));
631
	else
632
633
		info("%s: vers:%d %s %s\n", nodeid,
		     version, cp, command_array[i].cmdname);
634

Leigh B. Stoller's avatar
Leigh B. Stoller committed
635
	err = command_array[i].func(sock, nodeid, rdata, istcp, version);
636
637
638
639

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

641
 skipit:
642
643
644
	if (!istcp) 
		client_writeback_done(sock,
				      redirect ? &redirect_client : client);
645
	return 0;
646
647
648
649
650
}

/*
 * Accept notification of reboot. 
 */
651
COMMAND_PROTOTYPE(doreboot)
652
653
{
	MYSQL_RES	*res;	
654
655
	char		pid[64];
	char		eid[64];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
656
	char		gid[64];
657

658
659
660
661
662
663
664
665
	/*
	 * 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?
	 */
666
	info("doreload: %s: Clearing current_reloads\n", nodeid);
667
668
	if (mydb_update("delete from current_reloads where node_id='%s'",
		        nodeid)) {
669
670
	    error("doreload: %s: DB Error clearing current_reloads!\n",
		  nodeid);
671
672
673
	    return 1;
	}

674
675
676
677
678
679
	/*
	 * 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
680
	if (nodeidtoexp(nodeid, pid, eid, gid)) {
681
		info("REBOOT: %s: Node is free\n", nodeid);
682
683
684
		return 0;
	}

685
	info("REBOOT: %s: Node is in experiment %s/%s\n", nodeid, pid, eid);
686
687
688
689
690

	/*
	 * XXX This must match the reservation made in sched_reload
	 *     in the tbsetup directory.
	 */
691
692
	if (strcmp(pid, RELOADPID) ||
	    strcmp(eid, RELOADEID)) {
693
694
695
		return 0;
	}

696
697
698
699
	/*
	 * See if the node was in the reload state. If so we need to clear it
	 * and its reserved status.
	 */
700
701
	res = mydb_query("select node_id from scheduled_reloads "
			 "where node_id='%s'",
702
703
			 1, nodeid);
	if (!res) {
704
		error("REBOOT: %s: DB Error getting reload!\n", nodeid);
705
706
707
708
709
710
711
712
		return 1;
	}
	if ((int)mysql_num_rows(res) == 0) {
		mysql_free_result(res);
		return 0;
	}
	mysql_free_result(res);

713
714
	if (mydb_update("delete from scheduled_reloads where node_id='%s'",
			nodeid)) {
715
		error("REBOOT: %s: DB Error clearing reload!\n", nodeid);
716
717
		return 1;
	}
718
	info("REBOOT: %s cleared reload state\n", nodeid);
719
720

	if (mydb_update("delete from reserved where node_id='%s'", nodeid)) {
721
		error("REBOOT: %s: DB Error clearing reload!\n", nodeid);
722
723
		return 1;
	}
724
	info("REBOOT: %s cleared reserved state\n", nodeid);
725
726
727
728
729
730
731

	return 0;
}

/*
 * Return status of node. Is it allocated to an experiment, or free.
 */
732
COMMAND_PROTOTYPE(dostatus)
733
734
735
{
	char		pid[64];
	char		eid[64];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
736
	char		gid[64];
737
	char		nickname[128];
Mike Hibler's avatar
Mike Hibler committed
738
	char		buf[MYBUFSIZE];
739
740
741
742

	/*
	 * Now check reserved table
	 */
Leigh B. Stoller's avatar
Leigh B. Stoller committed
743
	if (nodeidtoexp(nodeid, pid, eid, gid)) {
744
		info("STATUS: %s: Node is free\n", nodeid);
745
746
747
		strcpy(buf, "FREE\n");
		client_writeback(sock, buf, strlen(buf), tcp);
		return 0;
748
749
	}

750
751
752
753
754
755
756
757
758
	/*
	 * 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);

759
	info("STATUS: %s: %s", nodeid, buf);
760
761
762
763
764
765
	return 0;
}

/*
 * Return ifconfig information to client.
 */
766
COMMAND_PROTOTYPE(doifconfig)
767
768
769
770
771
{
	MYSQL_RES	*res;	
	MYSQL_ROW	row;
	char		pid[64];
	char		eid[64];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
772
	char		gid[64];
Mike Hibler's avatar
Mike Hibler committed
773
	char		buf[MYBUFSIZE];
774
775
776
777
778
	int		control_net, nrows;

	/*
	 * Now check reserved table
	 */
Leigh B. Stoller's avatar
Leigh B. Stoller committed
779
	if (nodeidtoexp(nodeid, pid, eid, gid)) {
780
		info("IFCONFIG: %s: Node is free\n", nodeid);
781
782
783
784
785
786
787
788
		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)) {
789
		error("IFCONFIG: %s: No Control Network\n", nodeid);
790
791
792
793
794
795
		return 1;
	}

	/*
	 * Find all the interfaces.
	 */
796
797
798
	res = mydb_query("select card,IP,IPalias,MAC,current_speed,duplex "
			 "from interfaces where node_id='%s'",
			 6, nodeid);
799
	if (!res) {
800
		error("IFCONFIG: %s: DB Error getting interfaces!\n", nodeid);
801
802
803
804
		return 1;
	}

	if ((nrows = (int)mysql_num_rows(res)) == 0) {
805
		error("IFCONFIG: %s: No interfaces!\n", nodeid);
806
807
808
809
810
811
		mysql_free_result(res);
		return 1;
	}
	while (nrows) {
		row = mysql_fetch_row(res);
		if (row[1] && row[1][0]) {
812
813
814
815
			int card    = atoi(row[0]);
			char *speed  = "100";
			char *unit   = "Mbps";
			char *duplex = "full";
816

817
818
819
820
821
822
			/*
			 * 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.
			 */
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
			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;

843
844
845
846
847
848
849
			/*
			 * Tack on MAC, which should go up above after
			 * Sharks are sunk.
			 */
			strcat(buf, " MAC=");
			strcat(buf, row[3]);

850
851
852
853
854
855
856
857
858
859
860
861
862
			/*
			 * 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);

863
			strcat(buf, "\n");
Mike Hibler's avatar
Mike Hibler committed
864
			client_writeback(sock, buf, strlen(buf), tcp);
865
			info("IFCONFIG: %s", buf);
866
		}
867
	skipit:
868
869
870
871
872
873
874
875
876
877
		nrows--;
	}
	mysql_free_result(res);

	return 0;
}

/*
 * Return account stuff.
 */
878
COMMAND_PROTOTYPE(doaccounts)
879
880
881
882
883
{
	MYSQL_RES	*res;	
	MYSQL_ROW	row;
	char		pid[64];
	char		eid[64];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
884
	char		gid[64];
Mike Hibler's avatar
Mike Hibler committed
885
	char		buf[MYBUFSIZE];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
886
	int		nrows, gidint;
887
	int		shared = 0, tbadmin;
888

889
890
891
	if (! tcp) {
		error("ACCOUNTS: %s: Cannot give account info out over UDP!\n",
		      nodeid);
892
893
894
895
896
897
		return 1;
	}

	/*
	 * Now check reserved table
	 */
Leigh B. Stoller's avatar
Leigh B. Stoller committed
898
	if (nodeidtoexp(nodeid, pid, eid, gid)) {
899
		error("ACCOUNTS: %s: Node is free\n", nodeid);
900
901
902
		return 1;
	}

903
#ifdef  NOSHAREDEXPTS
904
905
906
907
	/*
	 * We have the pid name, but we need the GID number from the
	 * projects table to send over. 
	 */
Leigh B. Stoller's avatar
Leigh B. Stoller committed
908
909
	res = mydb_query("select unix_name,unix_gid from groups "
			 "where pid='%s'",
910
911
912
			 2, pid);
#else
	/*
Leigh B. Stoller's avatar
Leigh B. Stoller committed
913
	 * Get a list of gid/unix_gid for each group that is allowed
914
915
916
	 * access to the experiments nodes. This is the owner of the
	 * node, plus the additional pids granted access. 
	 */
Leigh B. Stoller's avatar
Leigh B. Stoller committed
917
918
919
	res = mydb_query("select g.unix_name,g.unix_gid from groups as g "
			 "left join exppid_access as a on g.pid=a.pid "
			 "where g.pid='%s' or "
920
921
922
			 "      (a.exp_pid='%s' and a.exp_eid='%s')",
			 2, pid, pid, eid);
#endif
923
	if (!res) {
924
		error("ACCOUNTS: %s: DB Error getting gids!\n", pid);
925
926
927
928
		return 1;
	}

	if ((nrows = (int)mysql_num_rows(res)) == 0) {
929
		error("ACCOUNTS: %s: No Project!\n", pid);
930
931
932
933
		mysql_free_result(res);
		return 1;
	}

934
935
936
	while (nrows) {
		row = mysql_fetch_row(res);
		if (!row[1] || !row[1][1]) {
937
			error("ACCOUNTS: %s: No Project GID!\n", pid);
938
939
940
941
			mysql_free_result(res);
			return 1;
		}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
942
943
		gidint = atoi(row[1]);
		sprintf(buf, "ADDGROUP NAME=%s GID=%d\n", row[0], gidint);
944
		client_writeback(sock, buf, strlen(buf), tcp);
945
		info("ACCOUNTS: %s", buf);
946
947
948

		nrows--;
	}
949
950
	mysql_free_result(res);

951
952
	/*
	 * Each time a node picks up accounts, decrement the update
Leigh B. Stoller's avatar
Leigh B. Stoller committed
953
954
955
	 * 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.
956
957
958
959
960
961
962
963
	 */
	if (mydb_update("update nodes set update_accounts=update_accounts-1 "
			"where node_id='%s' and update_accounts!=0",
			nodeid)) {
		error("ACCOUNTS: %s: DB Error setting exit update_accounts!\n",
		      nodeid);
	}
			 
964
965
966
	/*
	 * Now onto the users in the project.
	 */
967
#ifdef  NOSHAREDEXPTS
Leigh B. Stoller's avatar
Leigh B. Stoller committed
968
969
	res = mydb_query("select distinct "
			 "  u.uid,u.usr_pswd,u.unix_uid,u.usr_name, "
970
971
			 "  p.trust,p.pid,p.gid,g.unix_gid,u.admin, "
			 "  u.emulab_pubkey,u.home_pubkey "
Leigh B. Stoller's avatar
Leigh B. Stoller committed
972
973
974
975
976
			 "from users as u "
			 "left join group_membership as p on p.uid=u.uid "
			 "left join groups as g on p.pid=g.pid "
			 "where p.pid='%s' and p.gid='%s' "
			 "      and u.status='active' order by u.uid",
977
			 11, pid, eid, pid, gid);
978
#else
979
980
981
982
983
984
985
986
	/*
	 * See if a shared experiment. Used below.
	 */
	res = mydb_query("select shared from experiments "
			 "where pid='%s' and eid='%s'",
			 1, pid, eid);
	
	if (!res) {
987
		error("ACCOUNTS: %s: DB Error getting shared!\n", pid);
988
989
990
991
		return 1;
	}

	if ((nrows = (int)mysql_num_rows(res)) == 0) {
992
		error("ACCOUNTS: %s: No Experiment %s!\n", pid, eid);
993
994
995
996
997
998
999
		mysql_free_result(res);
		return 0;
	}
	row = mysql_fetch_row(res);
	shared = atoi(row[0]);
	mysql_free_result(res);
	
1000
1001
1002
1003
1004
1005
1006
1007
	/*
	 * 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.
	 */
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1008
1009
1010
	if (strcmp(pid, gid)) {
		res = mydb_query("select distinct "
			 "  u.uid,u.usr_pswd,u.unix_uid,u.usr_name, "
1011
			 "  p.trust,g.pid,g.gid,g.unix_gid,u.admin, "
1012
1013
			 "  u.emulab_pubkey,u.home_pubkey, "
			 "  UNIX_TIMESTAMP(u.usr_modified) "
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1014
1015
1016
1017
			 "from users as u "
			 "left join group_membership as p on p.uid=u.uid "
			 "left join groups as g on p.pid=g.pid "
			 "where ((p.pid='%s' and p.gid='%s')) "
1018
			 "      and p.trust!='none' "
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1019
			 "      and u.status='active' order by u.uid",
1020
			 12, pid, gid);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1021
1022
1023
1024
	}
	else {
		res = mydb_query("select distinct "
			 "  u.uid,u.usr_pswd,u.unix_uid,u.usr_name, "
1025
			 "  p.trust,g.pid,g.gid,g.unix_gid,u.admin, "
1026
1027
			 "  u.emulab_pubkey,u.home_pubkey, "
			 "  UNIX_TIMESTAMP(u.usr_modified) "
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1028
1029
1030
1031
			 "from users as u "
			 "left join group_membership as p on p.uid=u.uid "
			 "left join groups as g on "
			 "     p.pid=g.pid and p.gid=g.gid "
1032
			 "where ((p.pid='%s')) and p.trust!='none' "
1033
			 "      and u.status='active' order by u.uid",
1034
			 12, pid);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1035
	}
1036
#endif
1037
	if (!res) {
1038
		error("ACCOUNTS: %s: DB Error getting users!\n", pid);
1039
1040
1041
1042
		return 1;
	}

	if ((nrows = (int)mysql_num_rows(res)) == 0) {
1043
		error("ACCOUNTS: %s: No Users!\n", pid);
1044
1045
1046
1047
		mysql_free_result(res);
		return 0;
	}

1048
	row = mysql_fetch_row(res);
1049
	while (nrows) {
1050
		MYSQL_ROW	nextrow;
1051
1052
		MYSQL_RES	*pubkeys_res;	
		int		pubkeys_nrows, i, root = 0;
1053
1054
		int		auxgids[128], gcount = 0;
		char		glist[BUFSIZ];
1055

Leigh B. Stoller's avatar
Leigh B. Stoller committed
1056
1057
		gidint     = -1;
		tbadmin    = root = atoi(row[8]);
1058
1059
1060
1061
		
		while (1) {
			
			/*
1062
			 * The whole point of this mess. Figure out the
1063
1064
1065
1066
			 * main GID and the aux GIDs. Perhaps trying to make
			 * distinction between main and aux is unecessary, as
			 * long as the entire set is represented.
			 */
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1067
1068
1069
			if (strcmp(row[5], pid) == 0 &&
			    strcmp(row[6], gid) == 0) {
				gidint = atoi(row[7]);
1070
1071
1072
1073
1074
1075

				/*
				 * Only people in the main pid can get root
				 * at this time, so do this test here.
				 */
				if ((strcmp(row[4], "local_root") == 0) ||
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1076
1077
				    (strcmp(row[4], "group_root") == 0) ||
				    (strcmp(row[4], "project_root") == 0))
1078
1079
1080
					root = 1;
			}
			else {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
				int k, newgid = atoi(row[7]);
				
				/*
				 * Avoid dups, which can happen because of
				 * different trust levels in the join.
				 */
				for (k = 0; k < gcount; k++) {
				    if (auxgids[k] == newgid)
					goto skipit;
				}
				auxgids[gcount++] = newgid;
			skipit:
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
			}
			nrows--;

			if (!nrows)
				break;

			/*
			 * See if the next row is the same UID. If so,
			 * we go around the inner loop again.
			 */
			nextrow = mysql_fetch_row(res);
			if (strcmp(row[0], nextrow[0]))
				break;
			row = nextrow;
		}
		/*
		 * Okay, process the UID. If there is no primary gid,
		 * then use one from the list. Then convert the rest of
		 * the list for the GLIST argument below.
		 */
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1113
1114
		if (gidint == -1) {
			gidint = auxgids[--gcount];
1115
1116
1117
1118
		}
		glist[0] = '\0';
		for (i = 0; i < gcount; i++) {
			sprintf(&glist[strlen(glist)], "%d", auxgids[i]);
1119

1120
1121
1122
			if (i < gcount-1)
				strcat(glist, ",");
		}
1123

1124
1125
1126
1127
1128
1129
1130
		/*
		 * Override root when a shared experiment, except for
		 * TB admin people.
		 */
		if (shared && !tbadmin)
			root = 0;

1131