tmcd.c 70.2 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>
22
#include <paths.h>
23
24
#include <mysql/mysql.h>
#include "decls.h"
25
#include "config.h"
26
27
#include "ssl.h"
#include "log.h"
28
#include "tbdefs.h"
29

30
31
32
33
#ifdef EVENTSYS
#include "event.h"
#endif

34
35
36
/*
 * XXX This needs to be localized!
 */
37
38
39
#define FSPROJDIR	FSNODE ":" FSDIR_PROJ
#define FSGROUPDIR	FSNODE ":" FSDIR_GROUPS
#define FSUSERDIR	FSNODE ":" FSDIR_USERS
40
#define PROJDIR		"/proj"
41
#define GROUPDIR	"/groups"
42
43
44
45
#define USERDIR		"/users"
#define RELOADPID	"emulab-ops"
#define RELOADEID	"reloading"

46
47
48
#define TESTMODE
#define NETMASK		"255.255.255.0"

49
50
51
52
/* Defined in configure and passed in via the makefile */
#define DBNAME_SIZE	64
#define DEFAULT_DBNAME	TBDBNAME

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

71
72
73
74
75
/* thread support */
#define MAXCHILDREN	25
#define MINCHILDREN	5
static int	udpchild;
static int	numchildren;
76
static int	maxchildren = 15;
77
static volatile int killme;
78

79
80
81
82
83
#ifdef EVENTSYS
int			myevent_send(address_tuple_t address);
static event_handle_t	event_handle = NULL;
#endif

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

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);
115
COMMAND_PROTOTYPE(dotunnels);
116
COMMAND_PROTOTYPE(dovnodelist);
117
COMMAND_PROTOTYPE(doisalive);
118
119
120

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

153
154
155
156
157
158
159
160
/* 
 * Simple struct used to make a linked list of ifaces
 */
struct node_interface {
	char *iface;
	struct node_interface *next;
};

161
int	directly_connected(struct node_interface *interfaces, char *iface);
162

163
164
165
166
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"
167
 " -c num	   Specify number of servers (must be %d <= x <= %d)\n"
168
169
170
171
172
 "\n";

void
usage()
{
173
	fprintf(stderr, usagestr, MINCHILDREN, MAXCHILDREN);
174
175
176
	exit(1);
}

177
178
179
180
181
static void
cleanup()
{
	signal(SIGHUP, SIG_IGN);
	killme = 1;
182
	killpg(0, SIGHUP);
183
184
}

Mike Hibler's avatar
Mike Hibler committed
185
186
int
main(int argc, char **argv)
187
{
188
	int			tcpsock, udpsock, ch;
189
	int			length, i, status, pid;
Mike Hibler's avatar
Mike Hibler committed
190
	struct sockaddr_in	name;
191
192
	FILE			*fp;
	char			buf[BUFSIZ];
193
	struct hostent		*he;
194
	extern char		build_info[];
195

196
	while ((ch = getopt(argc, argv, "dp:c:X")) != -1)
197
198
199
200
201
202
		switch(ch) {
		case 'p':
			portnum = atoi(optarg);
			break;
		case 'd':
			debug++;
203
			break;
204
205
206
		case 'c':
			maxchildren = atoi(optarg);
			break;
207
208
209
210
211
#ifdef LBS
		case 'X':
			insecure = 1;
			break;
#endif
212
213
214
215
216
217
218
219
220
221
		case 'h':
		case '?':
		default:
			usage();
		}
	argc -= optind;
	argv += optind;

	if (argc)
		usage();
222
223
	if (maxchildren < MINCHILDREN || maxchildren > MAXCHILDREN)
		usage();
224

225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
#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
240

241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
	/*
	 * 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
256
	/*
257
	 * Setup TCP socket for incoming connections.
Mike Hibler's avatar
Mike Hibler committed
258
259
	 */

260
	/* Create socket from which to read. */
Mike Hibler's avatar
Mike Hibler committed
261
262
	tcpsock = socket(AF_INET, SOCK_STREAM, 0);
	if (tcpsock < 0) {
263
		pfatal("opening stream socket");
264
265
	}

266
	i = 1;
Mike Hibler's avatar
Mike Hibler committed
267
	if (setsockopt(tcpsock, SOL_SOCKET, SO_REUSEADDR,
268
		       (char *)&i, sizeof(i)) < 0)
269
		pwarning("setsockopt(SO_REUSEADDR)");;
270
271
272
273
	
	/* Create name. */
	name.sin_family = AF_INET;
	name.sin_addr.s_addr = INADDR_ANY;
274
	name.sin_port = htons((u_short) portnum);
Mike Hibler's avatar
Mike Hibler committed
275
	if (bind(tcpsock, (struct sockaddr *) &name, sizeof(name))) {
276
		pfatal("binding stream socket");
277
278
279
	}
	/* Find assigned port value and print it out. */
	length = sizeof(name);
Mike Hibler's avatar
Mike Hibler committed
280
	if (getsockname(tcpsock, (struct sockaddr *) &name, &length)) {
281
		pfatal("getsockname");
282
	}
Mike Hibler's avatar
Mike Hibler committed
283
	if (listen(tcpsock, 20) < 0) {
284
		pfatal("listen");
285
	}
286
287
	info("listening on TCP port %d\n", ntohs(name.sin_port));
	
Mike Hibler's avatar
Mike Hibler committed
288
289
290
291
292
293
294
	/*
	 * Setup UDP socket
	 */

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

	i = 1;
	if (setsockopt(udpsock, SOL_SOCKET, SO_REUSEADDR,
		       (char *)&i, sizeof(i)) < 0)
301
		pwarning("setsockopt(SO_REUSEADDR)");;
Mike Hibler's avatar
Mike Hibler committed
302
303
304
305
	
	/* Create name. */
	name.sin_family = AF_INET;
	name.sin_addr.s_addr = INADDR_ANY;
306
	name.sin_port = htons((u_short) portnum);
Mike Hibler's avatar
Mike Hibler committed
307
	if (bind(udpsock, (struct sockaddr *) &name, sizeof(name))) {
308
		pfatal("binding dgram socket");
Mike Hibler's avatar
Mike Hibler committed
309
310
311
312
313
	}

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

318
319
320
321
	signal(SIGTERM, cleanup);
	signal(SIGINT, cleanup);
	signal(SIGHUP, cleanup);

322
323
324
325
326
327
328
329
330
331
	/*
	 * 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);
	}

332
333
334
335
	/*
	 * 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. 
	 */
336
	while (1) {
337
		while (!killme && numchildren < maxchildren) {
338
339
			int doudp = (udpchild ? 0 : 1);
			if ((pid = fork()) < 0) {
340
				errorc("forking server");
341
				goto done;
Mike Hibler's avatar
Mike Hibler committed
342
			}
343
344
345
346
			if (pid) {
				if (doudp)
					udpchild = pid;
				numchildren++;
Mike Hibler's avatar
Mike Hibler committed
347
348
				continue;
			}
349
			/* Child does useful work! Never Returns! */
350
351
352
353
			signal(SIGTERM, SIG_DFL);
			signal(SIGINT, SIG_DFL);
			signal(SIGHUP, SIG_DFL);
			
354
355
356
357
358
			if (doudp) 
				udpserver(udpsock);
			else
				tcpserver(tcpsock);
			exit(-1);
359
		}
Mike Hibler's avatar
Mike Hibler committed
360

361
		/*
362
		 * Parent waits.
363
		 */
364
365
		pid = waitpid(-1, &status, 0);
		if (pid < 0) {
366
			errorc("waitpid failed");
367
			continue;
368
		}
369
		error("server %d exited with status 0x%x!\n", pid, status);
370
371
372
		numchildren--;
		if (pid == udpchild)
			udpchild = 0;
373
374
		if (killme && !numchildren)
			break;
375
376
	}
 done:
377
	CLOSE(tcpsock);
378
	close(udpsock);
379
	info("daemon terminating\n");
380
381
	exit(0);
}
382

383
/*
384
 * Listen for UDP requests. This is not a secure channel, and so this should
385
386
387
388
389
390
391
392
393
 * eventually be killed off.
 */
static void
udpserver(int sock)
{
	char			buf[MYBUFSIZE];
	struct sockaddr_in	client;
	int			length, cc;
	
394
	info("udpserver starting\n");
395
396
397
398
399
400
401
402
403
404

	/*
	 * 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)
405
406
				errorc("Reading UDP request");
			error("UDP Connection aborted\n");
407
			continue;
408
		}
409
410
411
412
413
414
415
		buf[cc] = '\0';
		handle_request(sock, &client, buf, 0);
	}
	exit(1);
}

/*
416
 * Listen for TCP requests.
417
418
419
420
421
422
423
424
 */
static void
tcpserver(int sock)
{
	char			buf[MYBUFSIZE];
	struct sockaddr_in	client;
	int			length, cc, newsock;
	
425
	info("tcpserver starting\n");
426
427
428
429
430
431

	/*
	 * Wait for TCP connections.
	 */
	while (1) {
		length  = sizeof(client);
432
		newsock = ACCEPT(sock, (struct sockaddr *)&client, &length);
433
		if (newsock < 0) {
434
			errorc("accepting TCP connection");
435
			continue;
436
		}
Mike Hibler's avatar
Mike Hibler committed
437
438

		/*
439
		 * Read in the command request.
Mike Hibler's avatar
Mike Hibler committed
440
		 */
441
		if ((cc = READ(newsock, buf, MYBUFSIZE - 1)) <= 0) {
442
			if (cc < 0)
443
444
445
				errorc("Reading TCP request");
			error("TCP connection aborted\n");
			CLOSE(newsock);
446
			continue;
447
		}
448
449
		buf[cc] = '\0';
		handle_request(newsock, &client, buf, 1);
450
		CLOSE(newsock);
451
452
453
	}
	exit(1);
}
Mike Hibler's avatar
Mike Hibler committed
454

455
456
457
458
static int
handle_request(int sock, struct sockaddr_in *client, char *rdata, int istcp)
{
	struct sockaddr_in redirect_client;
459
	int		   redirect = 0, isvnode = 0;
460
	char		   buf[BUFSIZ], *bp, *cp;
461
	char		   nodeid[TBDB_FLEN_NODEID];
462
	char		   vnodeid[TBDB_FLEN_NODEID];
463
464
	char		   class[TBDB_FLEN_NODECLASS];
	char		   type[TBDB_FLEN_NODETYPE];
465
	int		   i, islocal, err = 0;
466
	int		   version = DEFAULT_VERSION;
Mike Hibler's avatar
Mike Hibler committed
467

468
469
470
	/*
	 * Look for special tags. 
	 */
471
	bp = rdata;
472
473
474
475
476
477
478
479
480
481
482
483
484
	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
485

486
487
488
489
490
491
492
493
494
495
		/*
		 * 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);
496

497
498
			info("REDIRECTED from %s to %s\n",
			     inet_ntoa(redirect_client.sin_addr), buf);
499

500
501
502
503
504
505
506
507
508
509
510
511
512
			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));
513

514
515
516
517
518
			if (debug) {
				info("VNODEID %s\n", buf);
			}
			continue;
		}
519

520
521
522
523
524
		/*
		 * 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
525
526
527
		 *
		 * Note that rdata will point to any text after the command.
		 *
528
529
530
531
532
		 */
		if (*bp) {
			break;
		}
	}
533

534
535
536
	/* Start with default DB */
	strcpy(dbname, DEFAULT_DBNAME);

537
	/*
538
	 * Map the ip to a nodeid.
539
	 */
540
541
542
543
544
545
546
547
548
549
	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));
		}
550
551
552
553
554
555
556
557
		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. 
	 */
558
	if (!insecure && redirect &&
559
	    redirect_client.sin_addr.s_addr != myipaddr.s_addr) {
560
561
562
563
564
		char	buf1[32], buf2[32];
		
		strcpy(buf1, inet_ntoa(redirect_client.sin_addr));
		strcpy(buf2, inet_ntoa(client->sin_addr));
			
565
		info("%s INVALID REDIRECT: %s\n", buf1, buf2);
566
567
		goto skipit;
	}
568
569
570
571
572
573
574
575
576
577
578
579
580

#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) {
581
582
583
584
		if (!istcp) {
			/*
			 * Simple "isalive" support for remote nodes.
			 */
585
586
			doisalive(sock, nodeid, rdata, istcp,
				  islocal, type, version);
587
588
			goto skipit;
		}
589
		error("%s: Remote node connected without SSL!\n", nodeid);
590
591
		if (!insecure)
			goto skipit;
592
593
594
595
596
597
	}
#else
	/*
	 * When not compiled for ssl, do not allow remote connections.
	 */
	if (!islocal) {
598
599
600
601
		if (!istcp) {
			/*
			 * Simple "isup" daemon support!
			 */
602
603
			doisalive(sock, nodeid, rdata, istcp,
				  islocal, type, version);
604
605
606
			goto skipit;
		}
		error("%s: Remote node connected without SSL!\n", nodeid);
607
608
		if (!insecure)
			goto skipit;
609
	}
610
611
612
613
#endif
	/*
	 * Check for a redirect using the default DB. This allows
	 * for a simple redirect to a secondary DB for testing.
614
	 * Upon return, the dbname has been changed if redirected.
615
	 */
616
	if (checkdbredirect(nodeid)) {
617
618
619
		/* Something went wrong */
		goto skipit;
	}
620

621
622
623
624
	/*
	 * Figure out what command was given.
	 */
	for (i = 0; i < numcommands; i++)
625
		if (strncmp(bp, command_array[i].cmdname,
626
627
			    strlen(command_array[i].cmdname)) == 0)
			break;
Mike Hibler's avatar
Mike Hibler committed
628

629
	if (i == numcommands) {
630
		info("%s: INVALID REQUEST: %.8s\n", nodeid, bp);
631
632
		goto skipit;
	}
633

634
635
636
	/*
	 * Execute it.
	 */
637
638
639
640
641
#ifdef	WITHSSL
	cp = isssl ? "ssl:yes" : "ssl:no";
#else
	cp = "";
#endif
642
643
644
645
646
	/*
	 * XXX hack, don't log "log" contents,
	 * both for privacy and to keep our syslog smaller.
	 */
	if (command_array[i].func == dolog)
647
648
		info("%s: vers:%d %s %s log %d chars\n", nodeid, version,
		     istcp ? "TCP" : "UDP", cp, strlen(rdata));
649
	else
650
651
		info("%s: vers:%d %s %s %s\n", nodeid, version,
		     istcp ? "TCP" : "UDP", cp, command_array[i].cmdname);
652

653
654
	err = command_array[i].func(sock, nodeid, rdata, istcp,
				    islocal, type, version);
655
656
657
658

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

660
 skipit:
661
662
663
	if (!istcp) 
		client_writeback_done(sock,
				      redirect ? &redirect_client : client);
664
	return 0;
665
666
667
668
669
}

/*
 * Accept notification of reboot. 
 */
670
COMMAND_PROTOTYPE(doreboot)
671
672
{
	MYSQL_RES	*res;	
673
674
	char		pid[64];
	char		eid[64];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
675
	char		gid[64];
676

677
678
679
680
681
682
683
684
	/*
	 * 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?
	 */
685
	info("doreload: %s: Clearing current_reloads\n", nodeid);
686
687
	if (mydb_update("delete from current_reloads where node_id='%s'",
		        nodeid)) {
688
689
	    error("doreload: %s: DB Error clearing current_reloads!\n",
		  nodeid);
690
691
692
	    return 1;
	}

693
694
695
696
697
698
	/*
	 * 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
699
	if (nodeidtoexp(nodeid, pid, eid, gid)) {
700
		info("REBOOT: %s: Node is free\n", nodeid);
701
702
703
		return 0;
	}

704
	info("REBOOT: %s: Node is in experiment %s/%s\n", nodeid, pid, eid);
705
706
707
708
709

	/*
	 * XXX This must match the reservation made in sched_reload
	 *     in the tbsetup directory.
	 */
710
711
	if (strcmp(pid, RELOADPID) ||
	    strcmp(eid, RELOADEID)) {
712
713
714
		return 0;
	}

715
716
717
718
	/*
	 * See if the node was in the reload state. If so we need to clear it
	 * and its reserved status.
	 */
719
720
	res = mydb_query("select node_id from scheduled_reloads "
			 "where node_id='%s'",
721
722
			 1, nodeid);
	if (!res) {
723
		error("REBOOT: %s: DB Error getting reload!\n", nodeid);
724
725
726
727
728
729
730
731
		return 1;
	}
	if ((int)mysql_num_rows(res) == 0) {
		mysql_free_result(res);
		return 0;
	}
	mysql_free_result(res);

732
733
	if (mydb_update("delete from scheduled_reloads where node_id='%s'",
			nodeid)) {
734
		error("REBOOT: %s: DB Error clearing reload!\n", nodeid);
735
736
		return 1;
	}
737
	info("REBOOT: %s cleared reload state\n", nodeid);
738
739

	if (mydb_update("delete from reserved where node_id='%s'", nodeid)) {
740
		error("REBOOT: %s: DB Error clearing reload!\n", nodeid);
741
742
		return 1;
	}
743
	info("REBOOT: %s cleared reserved state\n", nodeid);
744
745
746
747
748
749
750

	return 0;
}

/*
 * Return status of node. Is it allocated to an experiment, or free.
 */
751
COMMAND_PROTOTYPE(dostatus)
752
753
754
{
	char		pid[64];
	char		eid[64];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
755
	char		gid[64];
756
	char		nickname[128];
Mike Hibler's avatar
Mike Hibler committed
757
	char		buf[MYBUFSIZE];
758
759
760
761

	/*
	 * Now check reserved table
	 */
Leigh B. Stoller's avatar
Leigh B. Stoller committed
762
	if (nodeidtoexp(nodeid, pid, eid, gid)) {
763
		info("STATUS: %s: Node is free\n", nodeid);
764
765
766
		strcpy(buf, "FREE\n");
		client_writeback(sock, buf, strlen(buf), tcp);
		return 0;
767
768
	}

769
770
771
772
773
774
775
776
777
	/*
	 * 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);

778
	info("STATUS: %s: %s", nodeid, buf);
779
780
781
782
783
784
	return 0;
}

/*
 * Return ifconfig information to client.
 */
785
COMMAND_PROTOTYPE(doifconfig)
786
787
788
789
790
{
	MYSQL_RES	*res;	
	MYSQL_ROW	row;
	char		pid[64];
	char		eid[64];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
791
	char		gid[64];
Mike Hibler's avatar
Mike Hibler committed
792
	char		buf[MYBUFSIZE];
793
794
795
796
797
	int		control_net, nrows;

	/*
	 * Now check reserved table
	 */
Leigh B. Stoller's avatar
Leigh B. Stoller committed
798
	if (nodeidtoexp(nodeid, pid, eid, gid)) {
799
		info("IFCONFIG: %s: Node is free\n", nodeid);
800
801
802
803
804
805
806
807
		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)) {
808
		error("IFCONFIG: %s: No Control Network\n", nodeid);
809
810
811
812
813
814
		return 1;
	}

	/*
	 * Find all the interfaces.
	 */
815
816
817
	res = mydb_query("select card,IP,IPalias,MAC,current_speed,duplex "
			 "from interfaces where node_id='%s'",
			 6, nodeid);
818
	if (!res) {
819
		error("IFCONFIG: %s: DB Error getting interfaces!\n", nodeid);
820
821
822
823
		return 1;
	}

	if ((nrows = (int)mysql_num_rows(res)) == 0) {
824
		error("IFCONFIG: %s: No interfaces!\n", nodeid);
825
826
827
828
829
830
		mysql_free_result(res);
		return 1;
	}
	while (nrows) {
		row = mysql_fetch_row(res);
		if (row[1] && row[1][0]) {
831
832
833
834
			int card    = atoi(row[0]);
			char *speed  = "100";
			char *unit   = "Mbps";
			char *duplex = "full";
835

836
837
838
839
840
841
			/*
			 * 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.
			 */
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
			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;

862
863
864
865
866
867
868
			/*
			 * Tack on MAC, which should go up above after
			 * Sharks are sunk.
			 */
			strcat(buf, " MAC=");
			strcat(buf, row[3]);

869
870
871
872
873
874
875
876
877
878
879
880
881
			/*
			 * 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);

882
			strcat(buf, "\n");
Mike Hibler's avatar
Mike Hibler committed
883
			client_writeback(sock, buf, strlen(buf), tcp);
884
			info("IFCONFIG: %s", buf);
885
		}
886
	skipit:
887
888
889
890
891
892
893
894
895
896
		nrows--;
	}
	mysql_free_result(res);

	return 0;
}

/*
 * Return account stuff.
 */
897
COMMAND_PROTOTYPE(doaccounts)
898
899
900
901
902
{
	MYSQL_RES	*res;	
	MYSQL_ROW	row;
	char		pid[64];
	char		eid[64];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
903
	char		gid[64];
Mike Hibler's avatar
Mike Hibler committed
904
	char		buf[MYBUFSIZE];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
905
	int		nrows, gidint;
906
	int		tbadmin;
907

908
909
910
	if (! tcp) {
		error("ACCOUNTS: %s: Cannot give account info out over UDP!\n",
		      nodeid);
911
912
913
914
915
916
		return 1;
	}

	/*
	 * Now check reserved table
	 */
Leigh B. Stoller's avatar
Leigh B. Stoller committed
917
	if (nodeidtoexp(nodeid, pid, eid, gid)) {
918
		error("ACCOUNTS: %s: Node is free\n", nodeid);
919
920
921
		return 1;
	}

922
923
        /*
	 * We need the unix GID and unix name for each group in the project.
924
	 */
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
	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 "
943
944
				 "where p.approved!=0 and "
				 "      FIND_IN_SET('%s',pcremote_ok)>0",
945
946
				 2, nodetype);
	}
947
	if (!res) {
948
		error("ACCOUNTS: %s: DB Error getting gids!\n", pid);
949
950
951
952
		return 1;
	}

	if ((nrows = (int)mysql_num_rows(res)) == 0) {
953
		error("ACCOUNTS: %s: No Project!\n", pid);
954
955
956
957
		mysql_free_result(res);
		return 1;
	}

958
959
960
	while (nrows) {
		row = mysql_fetch_row(res);
		if (!row[1] || !row[1][1]) {
961
			error("ACCOUNTS: %s: No Project GID!\n", pid);
962
963
964
965
			mysql_free_result(res);
			return 1;
		}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
966
967
		gidint = atoi(row[1]);
		sprintf(buf, "ADDGROUP NAME=%s GID=%d\n", row[0], gidint);
968
		client_writeback(sock, buf, strlen(buf), tcp);
969
		info("ACCOUNTS: %s", buf);
970
971
972

		nrows--;
	}
973
974
	mysql_free_result(res);

975
976
	/*
	 * Each time a node picks up accounts, decrement the update
Leigh B. Stoller's avatar
Leigh B. Stoller committed
977
978
979
	 * 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.
980
981
982
983
984
985
986
987
	 */
	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);
	}
			 
988
989
990
	/*
	 * Now onto the users in the project.
	 */
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
	if (islocal) {
		/*
		 * 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.
		 */
		if (strcmp(pid, gid)) {
			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) "
			     "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 p.trust!='none' "
			     "      and u.status='active' order by u.uid",
			     12, pid, gid);
		}
		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) "
			     "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 "
			     "where ((p.pid='%s')) and p.trust!='none' "
			     "      and u.status='active' order by u.uid",
			     12, pid);
		}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1029
1030
	}
	else {
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
		/*
		 * 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 user info for all of the users
		 * in those projects (crossed with group in the project). 
		 */
		res = mydb_query("select distinct  "
				 "u.uid,u.usr_pswd,u.unix_uid,u.usr_name, "
				 "m.trust,g.pid,g.gid,g.unix_gid,u.admin, "
				 "u.emulab_pubkey,u.home_pubkey, "
				 "UNIX_TIMESTAMP(u.usr_modified) "
				 "from projects as p "
				 "left join group_membership as m "
				 "  on m.pid=p.pid "
				 "left join groups as g on "
				 "  g.pid=m.pid and g.gid=m.gid "
				 "left join users as u on u.uid=m.uid "
1051
1052
				 "where p.approved!=0 "
				 "      and FIND_IN_SET('%s',pcremote_ok)>0 "
1053
1054
1055
1056
				 "      and m.trust!='none' "
				 "      and u.status='active' "
				 "order by u.uid",
				 12, nodetype);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1057
	}
1058

1059
	if (!res) {
1060
		error("ACCOUNTS: %s: DB Error getting users!\n", pid);
1061
1062
1063
1064
		return 1;
	}

	if ((nrows = (int)mysql_num_rows(res)) == 0) {
1065
		error("ACCOUNTS: %s: No Users!\n", pid);
1066
1067
1068
1069
		mysql_free_result(res);
		return 0;
	}

1070
	row = mysql_fetch_row(res);
1071
	while (nrows) {
1072
		MYSQL_ROW	nextrow;
1073
1074
		MYSQL_RES	*pubkeys_res;	
		int		pubkeys_nrows, i, root = 0;
1075
1076
		int		auxgids[128], gcount = 0;
		char		glist[BUFSIZ];
1077

Leigh B. Stoller's avatar
Leigh B. Stoller committed
1078
1079
		gidint     = -1;
		tbadmin    = root = atoi(row[8]);
1080
1081
1082
1083
		
		while (1) {
			
			/*
1084
			 * The whole point of this mess. Figure out the
1085
1086
1087
1088
			 * 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
1089
1090
1091
			if (strcmp(row[5], pid) == 0 &&
			    strcmp(row[6], gid) == 0) {
				gidint = atoi(row[7]);
1092
1093
1094
1095
1096
1097

				/*
				 * 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
1098
1099
				    (strcmp(row[4], "group_root") == 0) ||
				    (strcmp(row[4], "project_root") == 0))
1100
1101
1102
					root = 1;
			}
			else {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
				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:
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
			}
			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
1135
1136
		if (gidint == -1) {
			gidint = auxgids[--gcount];
1137
1138
1139
1140
		}
		glist[0] = '\0';
		for (i = 0; i < gcount; i++) {
			sprintf(&glist[strlen(glist)], "%d", auxgids[i]);
1141

1142
1143
1144
			if (i < gcount-1)
				strcat(glist, ",");
		}
1145

1146
1147
1148
1149
1150
1151
1152
1153
		if (vers < 4) {
			sprintf(buf,
				"ADDUSER LOGIN=%s "
				"PSWD=%s UID=%s GID=%d ROOT=%d NAME=\"%s\" "
				"HOMEDIR=%s/%s GLIST=%s\n",
				row[0], row[1], row[2], gidint, root, row[3],
				USERDIR, row[0], glist);
		}
1154
		else if (vers == 4) {
1155
			snprintf(buf, sizeof(buf) - 1,
1156
1157
1158
1159
1160
1161
1162
1163
1164
				"ADDUSER LOGIN=%s "
				"PSWD=%s UID=%s GID=%d ROOT=%d NAME=\"%s\" "
				"HOMEDIR=%s/%s GLIST=\"%s\" "
				"EMULABPUBKEY=\"%s\" HOMEPUBKEY=\"%s\"\n",
				row[0], row[1], row[2], gidint, root, row[3],
				USERDIR, row[0], glist,
				row[9] ? row[9] : "",
				row[10] ? row[10] : "");
		}
1165
1166
1167
1168
		else {
			sprintf(buf,
				"ADDUSER LOGIN=%s "
				"PSWD=%s UID=%s GID=%d ROOT=%d NAME=\"%s\" "
1169
				"HOMEDIR=%s/%s GLIST=\"%s\" SERIAL=%s\n",
1170
				row[0], row[1], row[2], gidint, root, row[3],
1171
				USERDIR, row[0], glist, row[11]);
1172
		}
1173
			
Mike Hibler's avatar