mkacct-ctrl.in 8.58 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
#
# 3) Called from command line as user with group_root for project 
85 86 87 88
#    to create a user account.
#
# 4  Called from web UI as some random user trying to change his own
#    account info or password.
89
# 
90
# 5) Called from command line as root.
91
#
92 93
my ($me) = getpwuid($UID) or
    fatal("$UID not in passwd file");
94

95
if ($UID && !TBAdmin($UID) && ($me ne $user)) {
96
    #
97
    # Check if group_root for the project.
98
    #
99 100 101 102 103 104 105 106
    $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");
107
    }
108
}
109

110
#
111 112 113 114 115 116 117 118
# 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).
119 120 121 122 123 124
#
$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");
125
}
126 127 128 129 130 131 132 133 134 135 136 137
@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;
138

139 140 141 142 143 144 145 146 147 148 149 150
#
# 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]);
}
151

152 153 154 155 156 157 158 159 160 161 162 163 164
#
# 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];
}
165

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

175 176 177 178
#
# Note hardwired control node. 
# 
my $control_node = $CONTROL;
179

180 181 182 183 184 185 186
#
# 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;
187

188 189 190 191 192 193 194 195 196 197
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")) { 
198 199
	print "Adding group $group to paper\n";

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

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

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

217 218 219 220 221 222 223 224 225 226 227 228
#
# 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";
}
229

230 231 232 233 234 235 236 237 238 239 240 241
#
# 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.");
242
    }
243 244 245
}
else {
    print "Updating user $user ($user_number) record on local node.\n";
246 247

    #
248 249 250 251 252
    # 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.");
253
    }
254 255
}

256 257 258 259 260 261 262 263
#
# 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;
264

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

268 269 270 271 272
    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");
273
    }
274 275 276
}
else {
    print "Updating user $user record on $control_node.\n";
277

278 279 280 281
    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.");
282
    }
283 284 285 286
}
if (system("$SSH $control_node $CHPASS -p $pswd $user")) {
    fatal("Could not change password for user $user on $control_node.\n");
}
287

288 289 290 291 292 293 294 295 296 297 298 299 300 301
#
# 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;
302

303 304 305 306 307 308 309 310 311
    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: $!");
312 313
}

314 315 316 317 318
#
# 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";
319

320 321 322 323 324
    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");
325 326
}

327 328 329 330 331 332 333 334
exit(0);

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

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