genelists.in 12.2 KB
Newer Older
1
#!/usr/bin/perl -wT
Leigh B. Stoller's avatar
Leigh B. Stoller committed
2
3
4

#
# EMULAB-COPYRIGHT
5
# Copyright (c) 2000-2005 University of Utah and the Flux Group.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
6
7
# All rights reserved.
#
8
use Fcntl ':flock';
9
use English;
10
use Getopt::Std;
11
12

sub usage() {
13
14
15
16
17
18
19
20
21
22
23
    print("Usage: genelists [-d] [-n] -a\n".
	  "Usage: genelists [-d] [-n] [-m] -u user\n".
	  "Usage: genelists [-d] [-n] -p project\n".
	  "Usage: genelists [-d] [-n] -t\n".
	  "where:\n".
	  "  -d    - Turn on debugging\n".
	  "  -n    - Impotent mode\n".
	  "  -u    - Generate lists for a user; add -m for new email address\n".
	  "  -p    - Generate lists for a project (includes subgroups)\n".
	  "  -t    - Generate activity lists\n".
	  "  -a    - Generate all email lists; careful ...\n");
24
25
    exit(-1);
}
26
27
28
29
30
31
32
33

sub ActiveUsers();
sub RecentUsers();
sub RecentProjects();
sub Users();
sub WideAreaPeople();
sub ProjectLeaders();
sub ProjectLists($$);
34
sub genelist($$$$);
35

36
37
38
39
40
41
42
43
my $optlist = "anu:p:tdm";
my $debug   = 0;
my $all     = 0;
my $update  = 0;
my $activity= 0;
my $impotent= 0;
my $pid;
my $user;
44
45
46
47

# Configure variables
my $TB		= "@prefix@";
my $TBOPS       = "@TBOPSEMAIL@";
48
my $USERS       = "@USERNODE@";
49
my $OURDOMAIN   = "@OURDOMAIN@";
50
51
my $TBACTIVE    = "@TBACTIVEARCHIVE@";
my $TBALL       = "@TBUSERSARCHIVE@";
52
my $PROJROOT	= "/proj";
53
my $GRPROOT	= "/groups";
54
my $ELISTS      = "$TB/lists";
55
56
my $MAILMANSUPPORT= @MAILMANSUPPORT@;
my $MMPROG	= "$TB/sbin/setmmlistmembers";
57
58

# Note no -n option. We redirect stdin from the new exports file below.
59
my $SSH		= "$TB/bin/sshtb -l root -host $USERS";
60
my $PROG	= "/usr/testbed/sbin/genelists.proxy";
61
my $lockfile    = "/var/tmp/testbed_genelists_lockfile";
62
my $tempfile    = "/var/tmp/testbed_genelists_tempfile";
63
my $SAVEUID	= $UID;
64

65
66
67
#
# Turn off line buffering on output
#
68
$| = 1;
69
70

# Load the Testbed support stuff.
71
72
73
use lib "@prefix@/lib";
use libdb;
use libtestbed;
74

75
76
77
78
#
# We don't want to run this script unless its the real version.
#
if ($EUID != 0) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
79
80
    die("*** $0:\n".
	"    Must be root! Maybe its a development version?\n");
81
82
}
# XXX Hacky!
83
if (0 && $TB ne "/usr/testbed") {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
84
85
    die("*** $0:\n".
	"    Wrong version. Maybe its a development version?\n");
86
}
87
88
89
90
91
92
93

#
# un-taint path
#
$ENV{'PATH'} = '/bin:/usr/bin:/usr/sbin:/usr/local/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

94
95
96
97
98
99
100
101
102
103
104
#
# Parse command arguments. Once we return from getopts, all that should
# left are the required arguments.
#
%options = ();
if (! getopts($optlist, \%options)) {
    usage();
}
if (@ARGV) {
    usage();
}
105
if (defined($options{"d"})) {
106
    $debug++;
107
}
108
if (defined($options{"a"})) {
109
110
111
112
113
114
115
    $all = 1;
}
if (defined($options{"m"})) {
    $update = 1;
}
if (defined($options{"t"})) {
    $activity = 1;
116
117
}
if (defined($options{"n"})) {
118
119
120
121
122
    $impotent = 1;
}
if (defined($options{"u"})) {
    $user = $options{"u"};
    
123
124
125
    #
    # Untaint.
    #
126
127
    if ($user =~ /^([-\w]+)$/) {
	$user = $1;
128
129
    }
    else {
130
	die("Tainted argument $user!\n");
131
132
    }
}
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151

if (defined($options{"p"})) {
    $pid = $options{"p"};
    
    #
    # Untaint.
    #
    if ($pid =~ /^([-\w]+)$/) {
	$pid = $1;
    }
    else {
	die("Tainted argument $pid!\n");
    }
}

if (defined($user) && defined($pid)) {
    usage();
}
if ($update && !defined($user)) {
152
153
154
    usage();
}

155
156
#
# We need to serialize this script to avoid a trashed map file. Use
157
# a dummy file in /var/tmp, opened for writing and flock'ed.
158
#
159
160
open(LOCK, ">>$lockfile") || fatal("Couldn't open $lockfile\n");
$count = 0;
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
if (flock(LOCK, LOCK_EX|LOCK_NB) == 0) {
    #
    # If we don't get it the first time, we wait for:
    # 1) The lock to become free, in which case we do our thing
    # 2) The time on the lock to change, in which case we wait for that process
    #    to finish
    #
    my $oldlocktime = (stat(LOCK))[9];
    my $gotlock = 0;
    while (1) {
	print "Another genelists in progress, waiting for it to finish\n";
	if (flock(LOCK, LOCK_EX|LOCK_NB) != 0) {
	    # OK, got the lock, we can do what we're supposed to
	    $gotlock = 1;
	    last;
	}
	$locktime = (stat(LOCK))[9];
	if ($locktime != $oldlocktime) {
	    $oldlocktime = $locktime;
	    last;
	}
	if ($count++ > 20)  {
	    fatal("Could not get the lock after a long time!\n");
	}
	sleep(1);
    }

    $count = 0;
    #
    # If we didn't get the lock, wait for the processes that did to finish
    #
    if (!$gotlock) {
193
194
195
196
197
198
199
200
	while (1) {
	    if ((stat(LOCK))[9] != $oldlocktime) {
		exit(0);
	    }
	    if (flock(LOCK, LOCK_EX|LOCK_NB) != 0) {
		close(LOCK);
		exit(0);
	    }
201
	    if ($count++ > 30)  {
202
		fatal("Process with the lock didn't finish after a long time!\n");
203
	    }
204
	    sleep(1);
205
	}
206
207
208
    }
}

209
210
211
212
213
214
#
# Perl-style touch(1)
#
my $now = time;
utime $now, $now, $lockfile;

215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
#
# Grab the mailman admin password for TBOPS.
#
my $admin_address;

if ($MAILMANSUPPORT) {
    my $mailman_password;
    
    if (! TBGetSiteVar("general/mailman/password", \$mailman_password)) {
	fatal("Could not mailman admin password from sitevars!");
    }
    $admin_address = "$TBOPS $mailman_password 'Emulab Operations'";
}
else {
    $admin_address = $TBOPS;
}

232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
ActiveUsers()
    if ($all || $activity || $update);

RecentUsers()
    if ($all || $activity || $update);

RecentProjects()
    if ($all || $activity || $update);

Users()
    if ($all || defined($user));

WideAreaPeople()
    if ($all || defined($user));

ProjectLeaders()
    if ($all || defined($user) || defined($pid));

if ($all || defined($user) || defined($pid)) {
    my $query;
    my $phash = {};
    my $query_result;
    
    if ($all) {
256
257
258
259
	$query = "select g.pid,g.gid from groups as g ".
		 "left join projects as p on p.pid=g.pid ".
		 "where p.approved=1 ";
	$query .= "order by g.pid,g.gid";
260
261
    }
    elsif ($user) {
262
263
	$query  = "select pid,gid from group_membership where uid='$user' ";
	$query .= "order by pid,gid";
264
265
    }
    else {
266
267
	$query  = "select pid,gid from groups where pid='$pid' ";
	$query .= "order by pid,gid";
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
    }

    if (! ($query_result = DBQuery($query))) {
	DBFatal("Getting Project List!");
    }
    while (my ($pid,$gid) = $query_result->fetchrow_array()) {
	ProjectLists($pid, $gid);
    }
}

#
# Close the lock file. Exiting releases it, but might as well.
#
close(LOCK);
exit 0;

#
# All active users on the testbed
#
sub ActiveUsers()
{
289
    my $userlist;
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
    my $query_result;

    print "Getting Active Users\n" if $debug;
    
    if (! ($query_result =
	   DBQuery("SELECT DISTINCT u.usr_email from experiments as e ".
		   "left join group_membership as p ".
		   "     on e.pid=p.pid and p.pid=p.gid ".
		   "left join users as u on u.uid=p.uid ".
		   "where u.status='active' and ".
		   "      e.state='active' ".
		   "order by u.usr_email"))) {
	DBFatal("Getting Active Users!");
    }
    $userlist = "$TBOPS\n".
	        "$TBACTIVE";
306

307
    genelist($query_result, $userlist, "emulab-active-users", 0);
308
309
310
311
312
313
314
315
316
317
318
319
320
321
}

#
# Recently active users.
# 
sub RecentUsers()
{
    my $userlist;
    my $query_result;

    my $limit = (60 * 60 * 24) * TBGetSiteVar("general/recently_active");
    print "Getting Recently Active Users\n" if $debug;

    if (! ($query_result =
322
323
324
325
	   DBQuery("select distinct u.usr_email from user_stats as s ".
		   "left join users as u on u.uid=s.uid ".
		   "where ((UNIX_TIMESTAMP(now()) - ".
		   "       UNIX_TIMESTAMP(s.last_activity)) <= $limit) ".
326
327
		   "order by u.usr_email"))) {
	DBFatal("Getting Recently Active Users!");
328
    }
329
330
    $userlist  = "$TBOPS\n";
    $userlist .= "$TBACTIVE";
331

332
    genelist($query_result, $userlist, "emulab-recently-active-users", 0);
333
334
335
336
337
338
339
340
341
342
343
344
345
346
}

#
# Recently active projects (members).
#
sub RecentProjects()
{
    my $userlist;
    my $query_result;

    my $limit = (60 * 60 * 24) * TBGetSiteVar("general/recently_active");
    print "Getting Recently Active Projects (members)\n" if $debug;

    if (! ($query_result =
347
348
349
350
351
352
353
	   DBQuery("select distinct u.usr_email from project_stats as s ".
		   "left join group_membership as g on ".
		   "  g.pid=s.pid and g.gid=g.pid ".
		   "left join users as u on u.uid=g.uid ".
		   "where u.status='active' and ".
		   "      ((UNIX_TIMESTAMP(now()) - ".
		   "       UNIX_TIMESTAMP(s.last_activity)) <= $limit) ".
354
355
		   "order by u.usr_email"))) {
	DBFatal("Getting Recently Active Projects!");
356
    }
357
358
    $userlist  = "$TBOPS\n";
    $userlist .= "$TBACTIVE";
359

360
    genelist($query_result, $userlist, "emulab-recently-active-projects", 0);
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
}

#
# All active users of the testbed.
# 
sub Users()
{
    my $userlist;
    my $query_result;

    print "Getting All Users\n" if $debug;

    if (! ($query_result =
	   DBQuery("SELECT DISTINCT usr_email FROM users ".
		   "where status='active' order by usr_email"))) {
	DBFatal("Getting Users!");
377
    }
378
379
    $userlist  = "$TBOPS\n";
    $userlist .= "$TBALL";
380
    
381
    genelist($query_result, $userlist, "emulab-allusers", 0);
382
383
}

384
385
386
#
# Special list for people approved to use the widearea-nodes.
#
387
388
389
390
391
392
393
394
395
396
sub WideAreaPeople()
{
    my $query_result =
	DBQueryFatal("SELECT DISTINCT u.usr_email from projects as p ".
		     "left join group_membership as m on m.pid=p.pid ".
		     "left join users as u on u.uid=m.uid ".
		     "where p.approved!=0 and p.pcremote_ok is not null ".
		     "      and m.trust!='none' and u.status='active' ".
		     "order by usr_email");

397
    genelist($query_result, "$TBOPS", "emulab-widearea-users", 0);
398
}
399

400
#
401
# Another list of project leaders.
402
#
403
404
405
sub ProjectLeaders()
{
    my $query_result =
406
407
408
409
	DBQueryFatal("SELECT DISTINCT u.usr_email ".
		     ($MAILMANSUPPORT ?
		      ", u.uid ,u.usr_name, u.mailman_password " : "") .
		     "  from projects as p ".
410
411
412
		     "left join users as u on u.uid=p.head_uid ".
		     "where p.approved!=0 ".
		     "order by usr_email");
413

414
    genelist($query_result, "$TBOPS", "emulab-project-leaders", 0);
415
}
416

417
#
418
# Regen project lists. 
419
#
420
421
422
sub ProjectLists($$)
{
    my ($pid, $gid) = @_;
423
424
    my $proj_result;

425
    print "Getting project members for $pid/$gid\n" if $debug;
426

427
    my $query_result =
428
429
430
431
	DBQueryFatal("SELECT distinct u.usr_email ".
		     ($MAILMANSUPPORT ?
		      ", u.uid ,u.usr_name, u.mailman_password " : "") .
		     " from group_membership as p ".
432
433
		     "left join users as u on u.uid=p.uid ".
		     "where p.pid='$pid' and p.gid='$gid' and ".
434
		     " p.trust!='none' and u.status='active' ".
435
		     "order by u.usr_email");
436

437
438
    if ($query_result->numrows) {
	if ($pid eq $gid) {
439
	    genelist($query_result, undef, "$pid-users", 1);
440
441
	}
	else {
442
	    genelist($query_result, undef, "$pid-$gid-users", 1);
443
444
445
446
447
448
449
	}
    }
}

#
# Generate and fire over a list.
#
450
sub genelist($$$$)
451
{
452
    my($query_result, $inituserlist, $listname, $usemailman) = @_;
453
454
455

    print "Processing $listname at: \t".time()." \t(".
      $query_result->numrows()." entries)\n" if $debug;
456

457
458
    open(LIST,"> $tempfile") ||
	fatal("Couldn't open $tempfile: $!\n");
459

460
461
462
    print LIST "#\n";
    print LIST "# WARNING! THIS FILE IS AUTOGENERATED. DO NOT EDIT!\n";
    print LIST "#\n";
463
464
465
    if (defined($inituserlist)) {
	print LIST "$inituserlist\n";
    }
466
467

    for ($i = 0; $i < $query_result->numrows; $i++) {
468
469
470
	my ($user_email, $uid, $user_name, $mailman_password) =
	    $query_result->fetchrow_array();
	
471
472
473
	if (! defined($user_email)) {
	    next;
	}
474
	if ($usemailman && $MAILMANSUPPORT) {
475
	    print LIST "$uid $user_email $mailman_password '$user_name'\n";
476
477
478
479
480
	}
	else {
	    print LIST "$user_email\n";
	    print "$user_email\n" if $debug>1;
	}
481
482
    }
    close(LIST);
483
484
485
486
487
488
    chmod(0664, $tempfile);

    if (! -d $ELISTS) {
	if (! mkdir($ELISTS, 0770)) {
	    fatal("Could not make directory $ELISTS: $!");
	}
489

490
491
492
493
494
495
496
	if (! chmod(0775, $ELISTS)) {
	    fatal("Could not chmod directory $ELISTS: $!");
	}
    }

    if (-e "$ELISTS/$listname" &&
	system("cmp -s $tempfile $ELISTS/$listname") == 0) {
497
498
	print "$listname has not changed. Skipping.\n"
	    if ($debug);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
499
	unlink("$tempfile");
500
501
502
	return;
    }

Leigh B. Stoller's avatar
Leigh B. Stoller committed
503
    system("/bin/cp -pf $tempfile $ELISTS/$listname") == 0 ||
504
505
	fatal("Could not move $tempfile to $ELISTS/$listname: $!");
    
506
507
508
    #
    # Fire the new file over to the fileserver to finish up.
    #
509
    if (!$impotent) {
510
511
512
513
514
515
516
517
518
519
520
521
522
523
	if ($usemailman && $MAILMANSUPPORT) {
	    my $optarg = ($debug ? "-d" : "");

	    $EUID = $UID;
	    system("$MMPROG $optarg $listname $tempfile") == 0 or
		fatal("Failed: $MMPROG $listname $tempfile: $?");
	    $EUID = 0;
	}
	else {
	    $UID = 0;
	    system("$SSH $PROG $listname < $tempfile") == 0 or
		fatal("Failed: $SSH $PROG $listname < $tempfile: $?");
	    $UID = $SAVEUID;
	}
524
    }
525
    unlink("$tempfile");
526
527
}

528
529
sub fatal {
  local($msg) = $_[0];
530
  SENDMAIL($TBOPS, "Failure Generating Email Lists", $msg);
531
532
  die($msg);
}