liblocsetup.pm 16.4 KB
Newer Older
1
2
3
#!/usr/bin/perl -wT
#
# EMULAB-COPYRIGHT
Mike Hibler's avatar
Mike Hibler committed
4
# Copyright (c) 2000-2005 University of Utah and the Flux Group.
5
6
7
8
# All rights reserved.
#

#
9
# CygWin specific routines and constants for the client bootime setup stuff.
10
11
12
13
14
#
package liblocsetup;
use Exporter;
@ISA = "Exporter";
@EXPORT =
Mike Hibler's avatar
Mike Hibler committed
15
16
    qw ( $RM $CHOWN $MOUNT $UMOUNT $CP $LN $CHMOD $TOUCH $EGREP
	 $NTS $NET $HOSTSFILE
17
	 $TMPASSWD $SFSSD $SFSCD $RPMCMD
18
	 os_account_cleanup os_accounts_start os_accounts_end os_accounts_sync
19
20
	 os_ifconfig_line os_etchosts_line
	 os_setup os_groupadd os_groupgid os_useradd os_userdel os_usermod os_mkdir
21
22
23
	 os_ifconfig_veth
	 os_routing_enable_forward os_routing_enable_gated
	 os_routing_add_manual os_routing_del_manual os_homedirdel
24
25
	 os_groupdel os_samba_mount 
	 os_getnfsmounts os_getnfsmountpoints os_noisycmd
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
	 os_fwconfig_line os_fwrouteconfig_line
       );

# Must come after package declaration!
use English;

# Load up the paths. Its conditionalized to be compatabile with older images.
# Note this file has probably already been loaded by the caller.
BEGIN
{
    if (-e "/etc/emulab/paths.pm") {
	require "/etc/emulab/paths.pm";
	import emulabpaths;
    }
    else {
	my $ETCDIR  = "/etc/rc.d/testbed";
	my $BINDIR  = "/etc/rc.d/testbed";
	my $VARDIR  = "/etc/rc.d/testbed";
	my $BOOTDIR = "/etc/rc.d/testbed";
    }
}

48
49
use librc;

50
#
51
# Various programs and things specific to CygWin on XP and that we want to export.
52
# 
Mike Hibler's avatar
Mike Hibler committed
53
54
$CP		= "/bin/cp";
$LN		= "/bin/ln";
55
56
$RM		= "/bin/rm";
$CHOWN		= "/bin/chown";
Mike Hibler's avatar
Mike Hibler committed
57
58
$CHMOD		= "/bin/chmod";
$TOUCH		= "/bin/touch";
59
$MOUNT		= "/bin/mount";
60
$UMOUNT		= "/bin/umount";
Mike Hibler's avatar
Mike Hibler committed
61
$EGREP		= "/bin/egrep -q";
62

63
# Cygwin.
64
65
66
$MKPASSWD	= "/bin/mkpasswd";
$MKGROUP	= "/bin/mkgroup";
$AWK		= "/bin/gawk";
67
$BASH		= "/bin/bash";
68

69
# Windows under Cygwin.
70
71
$NTS		= "/cygdrive/c/WINDOWS/system32";
$NET		= "$NTS/net";
72
73
$NETSH		= "$NTS/netsh";
$NTE		= "$NTS/drivers/etc";
Mike Hibler's avatar
Mike Hibler committed
74
75
76

#$HOSTSFILE	= "$NTE/hosts";
$HOSTSFILE	= "/etc/hosts";
77

78
79
80
#
# These are not exported
#
81
my $ADDUSERS     = "$BINDIR/addusers.exe";
82
83
my $IFCONFIGBIN = "$NETSH interface ip set address";
my $IFCONFIG    = "$IFCONFIGBIN name=\"%s\" source=static addr=%s mask=%s";
84
85
86
87
88
89
90
my $IFC_1000MBS  = "1000baseTx";
my $IFC_100MBS  = "100baseTx";
my $IFC_10MBS   = "10baseT";
my $IFC_FDUPLEX = "FD";
my $IFC_HDUPLEX = "HD";
my @LOCKFILES   = ("/etc/group.lock", "/etc/gshadow.lock");
my $MKDIR	= "/bin/mkdir";
91
my $RMDIR	= "/bin/rmdir";
92
my $GATED	= "/usr/sbin/gated";
Mike Hibler's avatar
Mike Hibler committed
93
my $ROUTE	= "$NTS/route";
94
95
my $SHELLS	= "/etc/shells";
my $DEFSHELL	= "/bin/tcsh";
96
97
my $winusersfile = "$BOOTDIR/winusers";
my $usershellsfile = "$BOOTDIR/usershells";
98

Russ Fish's avatar
Russ Fish committed
99
100
101
102
103
104
105
106
107
108
109
#
# system() with error checking.
#
sub mysystem($)
{
    my ($cmd) = @_;
    if (system($cmd) != 0) {
	warning("Failed: ($cmd), $!\n");
    }
}

110
111
112
113
#
# OS dependent part of cleanup node state.
# 
sub os_account_cleanup()
114
115
116
117
118
119
120
{
    # Undo what rc.mounts and rc.accounts did.  

    # The users list could be gotten from multiple places; let's use the /users
    # directory as truth.
    if (opendir(DIRHANDLE, "/users")) {
	while ($name = readdir(DIRHANDLE)) {
121
	    if ($name =~ m/^\.+/ || $name =~ m/^Administrator$/) {
122
123
124
125
126
		next;
	    }
	    print "Removing user: $name\n";

	    # There is always an NT account.
Russ Fish's avatar
Russ Fish committed
127
	    mysystem("$NET user $name /delete > /dev/null");
128
129

	    # There will only be an NT homedir if the user has logged in sometime.
130
131
132
	    system("$CHMOD -R 777 C:/'Documents and Settings'/$name");
	    system("$CHOWN -Rf root C:/'Documents and Settings'/$name");
	    system("$RM -rf C:/'Documents and Settings'/$name");
Russ Fish's avatar
Russ Fish committed
133
	    # It sometimes also makes user.PCnnn, user.PCnnn.000, etc.
134
135
136
	    system("$CHMOD -R 777 C:/'Documents and Settings'/$name.*");
	    system("$CHOWN -Rf root C:/'Documents and Settings'/$name.*");
	    system("$RM -rf C:/'Documents and Settings'/$name.*");
137
138

	    # Unmount the homedir so we can get to the mount point.
139
140
	    system("$UMOUNT /users/$name");
	    system("$RMDIR /users/$name");
141
142
143
144
145
146
147
	}
	closedir(DIRHANDLE);

	# Make the CygWin /etc/passwd and /etc/group files match Windows.
	os_accounts_sync();
    }

148
149
150
151
152
153
154
    # Clean out the user /sshkeys directories, leaving /sshkeys/root alone.
    if (opendir(DIRHANDLE, "/sshkeys")) {
	while ($name = readdir(DIRHANDLE)) {
	    if ($name =~ m/^\.+/ || $name =~ m/^root$/) {
		next;
	    }

155
156
157
	    # Open up an existing key dir to the root user.  Even though root
	    # is in the Administrators group, it's locked out by permissions.
	    mysystem("$CHMOD 777 /sshkeys/$name");
Russ Fish's avatar
Russ Fish committed
158
159
	    mysystem("$CHOWN -Rf root /sshkeys/$name");
	    mysystem("$RM -rf /sshkeys/$name");
160
161
	}
	closedir(DIRHANDLE);
162
163
    }

Russ Fish's avatar
Russ Fish committed
164
    # Clean out the /proj subdirectories.
165
166
167
168
169
170
171
172
    if (opendir(DIRHANDLE, "/proj")) {
	while ($name = readdir(DIRHANDLE)) {
	    if ($name =~ m/^\.+/) {
		next;
	    }
	    print "Removing project: $name\n";

	    # Unmount the project dir so we can get to the mount point.
Russ Fish's avatar
Russ Fish committed
173
174
	    mysystem("$UMOUNT /proj/$name");
	    mysystem("$RMDIR /proj/$name");
175
176
177
	}
    }

Russ Fish's avatar
Russ Fish committed
178
179
    # Just unmount /share, everybody gets one.
    mysystem("$UMOUNT /share");
180
181
182
183
184
185
}

# 
# Make the CygWin /etc/passwd and /etc/group files match Windows.
# 
sub os_accounts_sync()
186
187
188
189
{
    unlink @LOCKFILES;

    # Generate the CygWin password and group files from the registry users.
190
    # Note that the group membership is not reported into the CygWin files.
191
192
    print "Resetting the CygWin passwd and group files.\n";

193
194
    my $cmd = "$MKPASSWD -l | $AWK -F: '";
    $cmd   .=   'BEGIN{ OFS=":" } ';
195
    # Make root's UID zero.  Put non-root homedirs under /users, not /home.
196
197
198
199
200
201
    $cmd   .=   '{ if ($1=="root") $3="0"; else sub("/home/", "/users/"); print }'; 
    $cmd   .= "'";
    # Apply the users' shell preferences.
    $cmd   .= " | sed -f $usershellsfile"
	if (-e $usershellsfile);
    $cmd   .= " > /etc/passwd";
202
203
    ##print "$cmd\n";
    if (system("$cmd") != 0) {
204
	warning("Could not generate /etc/password file: $!\n");
205
206
	return -1;
    }
207

208
    $cmd  = "$MKGROUP -l | $AWK '";
209
210
211
212
213
    # Make a duplicate group line that is a wheel alias for Administrators.
    $cmd .= '/^Administrators:/{print "wheel" substr($0, index($0,":"))} {print}';
    $cmd .= "' > /etc/group";
    ##print "$cmd\n";
    if (system("$cmd") != 0) {
214
	warning("Could not generate /etc/group file: $!\n");
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
	return -1;
    }
    
    return 0;
}

#
# Generate and return an ifconfig line that is approriate for putting
# into a shell script (invoked at bootup).
#
sub os_ifconfig_line($$$$$$$;$$)
{
    my ($iface, $inet, $mask, $speed, $duplex, $aliases,
	$iface_type, $settings, $rtabid) = @_;
    my ($uplines, $downlines);

    if ($inet ne "") {
	$uplines  .= sprintf($IFCONFIG, $iface, $inet, $mask);
	$downlines = "$IFCONFIGBIN $iface down";
    }
    
    return ($uplines, $downlines);
}

#
# Specialized function for configing locally hacked veth devices.
#
sub os_ifconfig_veth($$$$$;$$$)
{
    return "";
}

#
# Generate and return an string that is approriate for putting
# into /etc/hosts.
#
sub os_etchosts_line($$$)
{
    my ($name, $ip, $aliases) = @_;
    
255
256
    # Note: space rather than tab after the host name on Windows.
    return sprintf("%s %s %s", $ip, $name, $aliases);
257
258
}

259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
#
# On Windows NT, accumulate an input file for the addusers command.
# See "AddUsers Automates Creation of a Large Number of Users",
# http://support.microsoft.com/default.aspx?scid=kb;en-us;199878
# 
# The file format is comma-delimited, as follows:
# 
# [Users]
# User Name,Full name,Password,Description,HomeDrive,Homepath,Profile,Script
# 
# [Global] or [Local]
# Group Name,Comment,UserName,...
# 
my @groupNames;
my %groupsByGid;
my %groupMembers;
sub os_accounts_start()
{
    # Remember group info to be put out at the end.
    @groupNames = ();
    %groupsByGid = ();
    %groupMembers = ();

    if (! open(WINUSERS, "> $winusersfile")) {
283
284
285
286
	warning("os_accounts_start: Cannot create $winusersfile .\n");
	return -1;
    }

287
288
289
    # Don't wipe out previous user shell preferences, just add new ones.
    if (! open(USERSHELLS, ">> $usershellsfile")) {
	warning("os_accounts_start: Cannot create or append to $usershellsfile .\n");
290
291
292
293
	return -1;
    }

    # Users come before groups in the addusers.exe account stream.
294
    # Notice the <CR><LF>'s!  It's a Windows file.
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
    print WINUSERS "[Users]\r\n";

    return 0;
}

#
# Remember the mapping from an existing group GID to its name.
#
sub os_groupgid($$)
{
    my($group, $gid) = @_;

    $groupsByGid{$gid} = $group;    # Remember the name associated with the gid.

    return 0;
}

312
313
314
315
316
317
318
#
# Add a new group
# 
sub os_groupadd($$)
{
    my($group, $gid) = @_;

319
320
321
322
    push(@groupNames, $group);      # Remember all of the group names.
    os_groupgid($group, $gid);

    return 0;
323
324
325
326
327
328
329
330
331
}

#
# Delete an old group
# 
sub os_groupdel($)
{
    my($group) = @_;

332
333
    # Unimplemented.
    return -1;
334
335
336
337
338
339
340
341
342
}

#
# Remove a user account.
# 
sub os_userdel($)
{
    my($login) = @_;

343
344
    # Unimplemented.
    return -1;
345
346
347
348
349
350
351
352
353
}

#
# Modify user group membership.
# 
sub os_usermod($$$$$$)
{
    my($login, $gid, $glist, $pswd, $root, $shell) = @_;

354
355
356
    # Unimplemented.
    return -1;

357
358
359
360
361
362
363
364
365
366
    ##if ($root) {
    ##	  $glist = join(',', split(/,/, $glist), "root");
    ##}
    ##if ($glist ne "") {
    ##	  $glist = "-G $glist";
    ##}
    ### Map the shell into a full path.
    ##$shell = MapShell($shell);
    ##
    ##return system("$USERMOD -s $shell -g $gid $glist -p '$pswd' $login");
367
368
369
370
371
372
373
374
375
}

#
# Add a user.
# 
sub os_useradd($$$$$$$$$)
{
    my($login, $uid, $gid, $pswd, $glist, $homedir, $gcos, $root, $shell) = @_;

376
377
    # Groups have to be created before we can add members.
    my $gname = $groupsByGid{$gid};
378
    warning("Missing group name for gid $gid.\n")
379
380
381
382
383
384
385
386
387
388
	if (!$gname);
    $groupMembers{$gname} .= "$login ";
    $groupMembers{'Administrators'} .= "$login "
	if ($root);
    foreach my $gid (split(/,/, $glist)) {
	$gname = $groupsByGid{$gid};
	if ($gname) {
	    $groupMembers{$gname} .= "$login ";
	}
	else {
389
	    warning("Missing group name for gid $gid.\n");
390
	}
391
    }
392
		     
393
394
    # Map the shell into a full path.
    $shell = MapShell($shell);
395
396
397
    # Change the ones that are different from the default from mkpasswd, /bin/bash.
    print USERSHELLS "/^$login:/s|/bin/bash\$|$shell|\n"
	if ($shell !~ "/bin/bash");
398

399
400
401
    # Use the leading 8 chars of the Unix MD5 passwd hash as a known random
    # password, both here and in Samba.  Skip over a "$1$" prefix.
    my $pwd = $pswd;
402
    $pwd =~ s/^(\$1\$)(.{8}).*/$2/;
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
    
    print WINUSERS "$login,$gcos,$pwd,,,,,\r\n";

    return 0;
}

#
# Finish the input for the addusers command.
#
sub os_accounts_end()
{
    # Dump out the group *creation* lines.
    print WINUSERS "[Local]\r\n";
    foreach my $grp (@groupNames) {
	# Ignore group membership here.  See "net localgroup" below.
	print WINUSERS "$grp,Emulab $grp group,\r\n";
    }
    close WINUSERS;
421
    close USERSHELLS;
422
423
424
425
426
427
428
429
430
       
    # Create the whole batch of groups and accounts listed in the file.
    # /p options: Users don't have to change passwords, and they never expire.
    print "Creating the Windows users and groups.\n";
    my $winfile = "C:/cygwin$winusersfile";
    $winfile =~ s|/|\\|g;
    my $cmd = "$ADDUSERS /c '$winfile' /p:le";
    ##print "    $cmd\n";
    if (system($cmd) != 0) {
431
	warning("AddUsers error ($cmd)\n");
432
433
	return -1;
    }
434
435
436
437
438
439
440
441
442

    # Add members into groups using the "net localgroup /add" command.
    # (Addusers only creates groups, it can't add a user to an existing group.)
    while (my($grp, $members) = each %groupMembers) {
	foreach my $mbr (split(/ /,$members)) {
	    print "  Adding $mbr to $grp.\n";
	    my $cmd = "$NET localgroup $grp $mbr /add > /dev/null";
	    ##print "    $cmd\n";
	    if (system($cmd) != 0) {
443
		warning("net localgroup error ($cmd)\n");
444
445
446
447
448
	    }
	}
    }

    # Make the CygWin /etc/passwd and /etc/group files match Windows.
449
    # Note that the group membership is not reported into the CygWin files.
450
    return os_accounts_sync();
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
}

#
# Remove a homedir. Might someday archive and ship back.
#
sub os_homedirdel($$)
{
    return 0;
}

#
# Create a directory including all intermediate directories.
#
sub os_mkdir($$)
{
    my ($dir, $mode) = @_;

    if (system("$MKDIR -p -m $mode $dir")) {
	return 0;
    }
    return 1;
}

#
# OS Dependent configuration. 
# 
sub os_setup()
{
    return 0;
}
    
#
# OS dependent, routing-related commands
#
sub os_routing_enable_forward()
{
Mike Hibler's avatar
Mike Hibler committed
487
    return "";
488
489
490
491
}

sub os_routing_enable_gated($)
{
Mike Hibler's avatar
Mike Hibler committed
492
    return "";
493
494
495
496
497
498
499
500
}

sub os_routing_add_manual($$$$$;$)
{
    my ($routetype, $destip, $destmask, $gate, $cost, $rtabid) = @_;
    my $cmd;

    if ($routetype eq "host") {
Mike Hibler's avatar
Mike Hibler committed
501
	$cmd = "$ROUTE add $destip $gate";
502
    } elsif ($routetype eq "net") {
Mike Hibler's avatar
Mike Hibler committed
503
	$cmd = "$ROUTE add $destip mask $destmask $gate";
504
    } elsif ($routetype eq "default") {
Mike Hibler's avatar
Mike Hibler committed
505
	$cmd = "$ROUTE add 0.0.0.0 $gate";
506
    } else {
507
	warning("Bad routing entry type: $routetype\n");
508
509
510
511
512
513
514
515
516
517
518
519
	$cmd = "";
    }

    return $cmd;
}

sub os_routing_del_manual($$$$$;$)
{
    my ($routetype, $destip, $destmask, $gate, $cost, $rtabid) = @_;
    my $cmd;

    if ($routetype eq "host") {
Mike Hibler's avatar
Mike Hibler committed
520
	$cmd = "$ROUTE delete $destip";
521
    } elsif ($routetype eq "net") {
Mike Hibler's avatar
Mike Hibler committed
522
	$cmd = "$ROUTE delete $destip";
523
    } elsif ($routetype eq "default") {
Mike Hibler's avatar
Mike Hibler committed
524
	$cmd = "$ROUTE delete 0.0.0.0";
525
    } else {
526
	warning("Bad routing entry type: $routetype\n");
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
	$cmd = "";
    }

    return $cmd;
}

# Map a shell name to a full path using /etc/shells
sub MapShell($)
{
   my ($shell) = @_;

   if ($shell eq "") {
       return $DEFSHELL;
   }

   my $fullpath = `grep '/${shell}\$' $SHELLS`;

   if ($?) {
       return $DEFSHELL;
   }

   # Sanity Check
   if ($fullpath =~ /^([-\w\/]*)$/) {
       $fullpath = $1;
   }
   else {
       $fullpath = $DEFSHELL;
   }
   return $fullpath;
}

558
sub os_samba_mount($$$)
559
560
561
{
    my ($local, $host, $verbose) = @_;

562
    # Unmount each one first, ignore errors.
Russ Fish's avatar
Russ Fish committed
563
    system("$UMOUNT $local");
564

565
566
    # Make the CygWin mount from the Samba path to the local mount point directory.
    my $sambapath = $local;
567
568
569
570
    $sambapath =~ s|^/proj/(.*)|proj-$1|;
    $sambapath =~ s|^/group/(.*)/(.*)|$1-$2|;
    $sambapath =~ s|.*/(.*)|$1|;
    $sambapath = "//$host/$sambapath";
571
572
    if (! -e $local) {
	print "os_samba_mount: Making CygWin '$local' mount point directory.\n"
573
	    if ($verbose);
574
575
	if (! os_mkdir($local, "0755")) {  # Will make whole path if necessary.
	    warning("os_samba_mount: Could not make mount point $local.\n");
576
577
	}
    }
578
579
    elsif (! -d $local) {
	warning("os_samba_mount: Mount point $local is not a directory.\n");
580
    }
581
    print "Mounting '$local' from '$sambapath'.\n"
582
	if ($verbose);
583
584
585
586
587

    # If we don't turn on the -E/--no-executable flag, CygWin mount warns us:
    #     mount: defaulting to '--no-executable' flag for speed since native path
    #            references a remote share.  Use '-f' option to override.
    # Even with -E, exe's and scripts still work properly, so put it in.
588
    $cmd = "$MOUNT -f -E $sambapath $local";
589
    if (system($cmd) != 0) {
590
	warning("os_samba_mount: Failed, $cmd.\n");
591
592
593
594
    }
}

# Extract the local mount point from a remote NFS mount path.
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
sub os_mountlocal($)
{
    my ($remote) = @_;
    my $local = $remote;
    $local =~ s|^.*:||;			# Remove server prefix.
    $local =~ s|^/q/proj/|/proj/|;	# Remove /q prefix from /proj.
    return $local;
}

# Execute a noisy bash command, throwing away the output unless we ask for it.
sub os_noisycmd($$)
{
    my ($cmd, $verbose) = @_;
    my $bashcmd = "$BASH -c '$cmd'" . ($verbose ? "" : " > /dev/null");
    my $ret = system($bashcmd);
    ##print "os_noisycmd cmd '$cmd', ret $ret\n";
    return $ret
}

sub os_fwconfig_line($@)
{
    my ($fwinfo, @fwrules) = @_;
    my ($upline, $downline);
618
    my $errstr = "*** WARNING: Windows firewall not implemented\n";
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656

    warn $errstr;
    $upline = "echo $errstr; exit 1";
    $downline = "echo $errstr; exit 1";

    return ($upline, $downline);
}

sub os_fwrouteconfig_line($$$)
{
    my ($orouter, $fwrouter, $routestr) = @_;
    my ($upline, $downline);

    #
    # XXX assume the original default route should be used to reach servers.
    #
    # For setting up the firewall, this means we create explicit routes for
    # each host via the original default route.
    #
    # For tearing down the firewall, we just remove the explicit routes
    # and let them fall back on the now re-established original default route.
    #
    $upline  = "for vir in $routestr; do\n";
    $upline .= "        $ROUTE delete \$vir >/dev/null 2>&1\n";
    $upline .= "        $ROUTE add -host \$vir gw $orouter || {\n";
    $upline .= "            echo \"Could not establish route for \$vir\"\n";
    $upline .= "            exit 1\n";
    $upline .= "        }\n";
    $upline .= "    done";

    $downline  = "for vir in $routestr; do\n";
    $downline .= "        $ROUTE delete \$vir >/dev/null 2>&1\n";
    $downline .= "    done";

    return ($upline, $downline);
}

1;