mkacct-ctrl.in 8.46 KB
Newer Older
1 2
#!/usr/local/bin/perl -wT
use English;
3 4 5 6 7 8 9 10

#
# Create accounts.
#
# XXX - The control node is hardwired. Look for $CONTROL.
#
# usage: mkacct-ctrl <userid>
# 
11

12 13 14
#
# Configure variables
#
15
my $TB      = "@prefix@";
16
my $TBOPS   = "@TBOPSEMAIL@";
17

18
my $HOMEDIR = "/users";
19
my $PBAG    = "$TB/sbin/paperbag";
Robert Ricci's avatar
Robert Ricci committed
20
my $SSH     = "$TB/bin/sshtb";
21
my $CONTROL = "users.emulab.net";
22 23 24 25
my $GROUPADD= "/usr/sbin/pw groupadd";
my $USERADD = "/usr/sbin/pw useradd";
my $USERMOD = "/usr/sbin/pw usermod";
my $CHPASS  = "/usr/bin/chpass";
26
my $GENELISTS = "$TB/sbin/genelists";
27

28 29 30
my $user;
my @db_row;
my $query_result;
31

32 33 34 35 36 37
#
# We don't want to run this script unless its the real version.
#
if ($EUID != 0) {
    die("Must be root! Maybe its a development version?");
}
38

39 40 41 42 43
#
# Untaint the path
# 
$ENV{'PATH'} = "/bin:/usr/bin";
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
44

45 46 47 48
#
# Turn off line buffering on output
#
$| = 1;
49

50 51 52 53 54 55 56 57 58 59 60 61
#
# Load the Testbed support stuff. 
#
push(@INC, "$TB/lib");
require libtestbed;
require libdb;

#
# Check args.
#
if ($#ARGV < 0) {
    die("Usage: mkacct-ctrl <userid>\n");
62
}
63
$user = $ARGV[0];
64 65

#
66
# Untaint the argument.
67
#
68 69 70 71 72 73 74 75 76 77 78 79
if ($user =~ /^([a-z0-9]+)$/i) {
    $user = $1;
}
else {
    die("Invalid uid '$user' contains illegal characters.\n");
}

#
# Figure out who called us. Possible scenarios:
#
# 1) Called from web UI as some TB admin user to create a project head's 
#    account for a new project.
80 81
#
# 2) Called as user with group_root for project to create a user
82
#    account.
83 84 85 86 87 88
#
# 3) Called from command line as user with group_root for project 
#    to create a user account. 
# 
# 4) Called from command line as root.
#
89 90 91
if ($UID && !TBAdmin($UID)) {
    my ($me) = getpwuid($UID) or
	fatal("$UID not in passwd file");
92 93

    #
94
    # Check if group_root for the project.
95
    #
96 97 98 99 100 101 102 103
    $query_result =
	DBQueryFatal("select p1.trust from proj_memb as p1 ".
		     "left join proj_memb as p2 on p2.pid=p1.pid ".
		     "where p1.uid='$me' and ".
		     "p2.uid='$user' and p1.trust='group_root'");

    if ($query_result->numrows == 0) {
	die("$0: $me does not have enough permission in ${user}'s project");
104
    }
105
}
106

107
#
108 109 110 111 112 113 114 115
# Run genelists to update the email lists. This is a convenient
# spot to do this. Errors are non-fatal; the testbed list will
# will find out about problems via email from genelists.
#
system("$GENELISTS");

#
# Get the user info (the user being created).
116 117 118 119 120 121
#
$query_result =
    DBQueryFatal("select usr_pswd,unix_uid,usr_name,usr_email ".
		 "from users where uid='$user'");
if ($query_result->numrows == 0) {
    fatal("$user is not in the DB. This is bad.\n");
122
}
123 124 125 126 127 128 129 130 131 132 133 134
@db_row         = $query_result->fetchrow_array();
my $pswd        = $db_row[0];
my $user_number = $db_row[1]; 
my $fullname    = $db_row[2];
my $user_email  = $db_row[3];

#
# Get the group (projects user belongs to) names and numbers. 
# 
my @groupnames;
my %groupnumbers;
my @grouplist;
135

136 137 138 139 140 141 142 143 144 145 146 147
#
# Form a list project membership names.
# 
$query_result =
    DBQueryFatal("select pid from proj_memb where ".
		 "uid='$user' and trust!='none'");
if ($query_result->numrows == 0) {
    fatal("$user is not in any groups!\n");
}
while (@db_row = $query_result->fetchrow_array() ) {
    push(@groupnames, $db_row[0]);
}
148

149 150 151 152 153 154 155 156 157 158 159 160 161
#
# Now join that list with the projects information to the unix gids.
# 
$query_result =
    DBQueryFatal("select pid,unix_gid from projects where ".
		 join(" or ", map ( "pid='$_'", @groupnames)));
if ($query_result->numrows == 0) {
    fatal("Could not get the unix GIDs for the projects!\n");
}
# Should probably check to make sure the counts match.
while (@db_row = $query_result->fetchrow_array()) {
    $groupnumbers{$db_row[0]} = $db_row[1];
}
162

163 164 165 166 167 168 169 170
#
# Add some special cases for admin types. Note hardwired gids!
#
if (TBAdmin($user)) {
    push(@groupnames, "wheel", "flux");
    $groupnumbers{"wheel"} = 0;
    $groupnumbers{"flux"}  = 601;
}
171

172 173 174 175
#
# Note hardwired control node. 
# 
my $control_node = $CONTROL;
176

177 178 179 180 181 182 183
#
# Create groups on both the control and operations nodes. We assume
# FreeBSD for both. 
# 
# All this stuff must be done as root (ssh).
#
$UID = $EUID;
184

185 186 187 188 189 190 191 192 193 194
foreach my $group ( @groupnames ) {
    my $group_number = $groupnumbers{$group};

    print "Processing group $group (gid $group_number)\n";
    
    # 
    # Create group locally if it does not exist. egrep returns 1 when
    # no matches are found.
    #
    if (system("egrep -q -s '^${group}:' /etc/group")) { 
195 196
	print "Adding group $group to paper\n";

197 198 199
	if (system("$GROUPADD $group -g $group_number")) {
	    fatal("Could not add $group ($group_number) to local node!\n");
	}
200
    }
201

202
    #
203
    # Create group on the control node if it does not exist.
204
    #
205 206
    if (system("$SSH $control_node egrep -q -s '^${group}:' /etc/group")) {
	print "Adding group $group to $control_node.\n";
207

208 209 210
	if (system("$SSH $control_node $GROUPADD $group -g $group_number")) {
	    fatal("Could not add $group ($group_number) to $control_node!\n");
	}
211
    }
212
}
213

214 215 216 217 218 219 220 221 222 223 224 225
#
# Construct an appropriate group list for the pw commands. Main project
# is the first on the list, and that becomes the primary group. The rest
# (if any) of the groups become a comma separated list for the -G option.
#
my $groupargument = " ";
my $project       = shift @groupnames;
my $grouplist     = join(",",@groupnames);

if ($grouplist) {
    $groupargument = "-G $grouplist";
}
226

227 228 229 230 231 232 233 234 235 236 237 238
#
# Make user on local. We don't give them a password since they are not
# allowed to log in, except via paperbaf. Be sure not overwrite the shell
# for a user who has a real shell. 
#
if (system("egrep -q -s '^${user}:' /etc/passwd")) {
    print "Adding user $user ($user_number) to local node.\n";

    if (system("$USERADD $user -u $user_number -c \"$fullname\" ".
	       "-k /usr/share/skel -m -d $HOMEDIR/$user ".
	       "-g $project $groupargument -s $PBAG")) {
	fatal("Could not add user $user to local node.");
239
    }
240 241 242
}
else {
    print "Updating user $user ($user_number) record on local node.\n";
243 244

    #
245 246 247 248 249
    # MAKE SURE not to update the shell
    #
    if (system("$USERMOD $user -u $user_number -c \"$fullname\" ".
	       "-g $project $groupargument")) {
	fatal("Could not modify user $user on local node.");
250
    }
251 252
}

253 254 255 256 257 258 259 260
#
# Make user account on control node. We do the password setup as separate
# step since thats easier than trying to both via ssh.
#
# Quote special chars for ssh and the shell on the other side
#
$fullname =~ s/\"/\'/g;
$fullname =~ s/([^\\])([\'\"\(\)])/$1\\$2/g;
261

262 263
if (system("$SSH $control_node egrep -q -s '^${user}:' /etc/passwd")) {
    print "Adding user $user ($user_number) to $control_node.\n";
264

265 266 267 268 269
    if (system("$SSH $control_node '$USERADD ".
	       "$user -u $user_number -c \"$fullname\" ".
	       "-k /usr/share/skel -m -d $HOMEDIR/$user -g $project ".
	       "$groupargument -s /bin/tcsh'")) {
	fatal("Could not add user $user ($user_number) to $control_node.\n");
270
    }
271 272 273
}
else {
    print "Updating user $user record on $control_node.\n";
274

275 276 277 278
    if (system("$SSH $control_node '$USERMOD ".
	       "$user -u $user_number ".
	       "-c \"$fullname\" -g $project $groupargument'")) {
	fatal("Could not modify user $user record on $control_node.");
279
    }
280 281 282 283
}
if (system("$SSH $control_node $CHPASS -p $pswd $user")) {
    fatal("Could not change password for user $user on $control_node.\n");
}
284

285 286 287 288 289 290 291 292 293 294 295 296 297 298
#
# Set up the ssh key, but only if not done so already.
#
if (! -e "$HOMEDIR/$user/.ssh/" ) {
    print "Setting up ssh configuration for $user.\n";
    
    mkdir("$HOMEDIR/$user/.ssh", 0700) or
	fatal("Could not mkdir $HOMEDIR/$user/.ssh: $!");
    chown($user_number, $groupnumbers{$project}, "$HOMEDIR/$user/.ssh") or
	fatal("Could not chown $HOMEDIR/$user/.ssh: $!");
    
    # Run commands below as the user
    $EUID = $user_number;
    $UID  = $EUID;
299

300 301 302 303 304 305 306 307 308
    if (system("/usr/bin/ssh-keygen -P '' -f $HOMEDIR/$user/.ssh/identity")) {
	fatal("Failure in ssh-keygen");
    }
    if (system("/bin/cp $HOMEDIR/$user/.ssh/identity.pub ".
	       "$HOMEDIR/$user/.ssh/authorized_keys")) {
	fatal("Copying over $HOMEDIR/$user/.ssh/identity.pub to auth keys");
    }
    chmod(0600, "$HOMEDIR/$user/.ssh/authorized_keys") or
	fatal("Could not chown $HOMEDIR/$user/.ssh/authorized_keys: $!");
309 310
}

311 312 313 314 315
#
# Set up a .forward file so that any email to them gets forwarded off.
#
if (! -e "$HOMEDIR/$user/.forward" ) {
    print "Setting up .forward file for $user.\n";
316

317 318 319 320 321
    if (system("echo \"$user_email\" > $HOMEDIR/$user/.forward")) {
	fatal("Could not create $HOMEDIR/$user/.forward");
    }
    chmod(0644, "$HOMEDIR/$user/.forward") or
	fatal("Could not chown $HOMEDIR/$user/.forward");
322 323
}

324 325 326 327 328 329 330 331
exit(0);

sub fatal {
    local($msg) = $_[0];

    SENDMAIL($TBOPS, "TESTBED: mkacct-ctrl Failed", $msg);
    die("$0: $msg");
}