add-users
1: #!/bin/bash -
2: # Script to add many student accounts at once. This is the
3: # Fedora version. Run as root only.
4: # See also the "disable-user" script to disable accounts before
5: # permanently removing them with 'remove-user'.
6: # TODO: currently invalid inputs abort program. This should be
7: # changed to a loop to try 'n' times (say 3) before aborting.
8: # TODO: Add option for setting account expire date (for ua users).
9: # TODO: change email subject to include the prefix (class name)
10: # TODO: Save passwd list in ~/tmp files.
11: # TODO: Add better defaults for all options.
12: # TODO: code review: POSIX has added many shell features recently. The
13: # script should also serve as a model.
14: #
15: # $Id: add-users,v 1.11 2016/05/04 06:16:52 wpollock Exp $
16: #
17: # Originally written 2002 by Wayne Pollock, Tampa FL USA.
18:
19: PATH=/bin:/sbin:/usr/bin:/usr/sbin
20:
21: PROG=${0##*/}
22: HOST=$(uname -n)
23: INSTRUCTOR=""
24: CREATE_ADDITIONAL_GRP="n"
25: ADD_INST_TO_ADDITIONAL_GRP="n"
26: ADD_INST_TO_STUTENT_GRPS="n"
27:
28: md5crypt()
29: {
30: printf '%s\n' "$1" |openssl passwd -1 -stdin
31: }
32:
33: # A fancy separator line with optional centered title,
34: # of 68 characters ("-") wide.
35:
36: drawLine()
37: {
38: local num line title
39: title="$1"
40: let "num = ( 68 - ${#title} ) / 2"
41: line=$(perl -e "print \"-\" x $num;")
42: printf '\n%s' "$line"
43: [ "$1" ] && printf '%s' " $1 "
44: printf '%s\n\n' "$line"
45: echo
46: }
47:
48: getInst()
49: {
50: local CURR_USER instructor
51:
52: # "id" doesn't always (?) report the real UID, so prefer "logname":
53: if [ -x /usr/bin/logname ]
54: then CURR_USER=`logname`
55: else CURR_USER=`id -run`
56: fi
57:
58: printf '%s' "What is your account name (default: $CURR_USER)? " >/dev/tty
59: read instructor
60: if [ "$instructor" = "" ]
61: then instructor=$CURR_USER
62: fi
63: if ! grep -e "^$instructor:" /etc/passwd >/dev/null 2>&1
64: then
65: echo "No user account \"$instructor\" found!" >&2
66: echo "Exiting..." >&2
67: exit 2
68: fi
69: echo "$instructor"
70: }
71:
72: if [ "`id -u`" != "0" ]
73: then echo "You must be root to run this script. (Try \"sudo $PROG\".)"
74: exit 1
75: fi
76:
77: drawLine "Class Account Creation Wizard"
78:
79: cat <<\EOF
80: Created accounts have the form <prefix><number>
81: (e.g. "rab01", "rab02", ...
82:
83: Enter class prefix(es) (e.g.: ua ub uc)
84: EOF
85: printf "A good choice is the first few letters of the instructor's name: "
86: read CLASSES
87: set -- $CLASSES
88: if [ $# = 0 ]
89: then echo "No classes entered, good-bye!"
90: exit 1
91: fi
92:
93: drawLine "Number of Accounts"
94:
95: echo "Enter number of accounts to create per class (e.g.: 25)."
96: echo "(Note that one additional account (numbered \"00\") will be"
97: echo "created for the instructor to use, as a demo for example.)"
98: echo
99: printf '%s' "How many student accounts should be created? "
100: read NUM JUNK
101: if [ -z "$NUM" ]
102: then echo "No value entered, good-bye!"
103: exit 1
104: fi
105: if [ "$JUNK" ]
106: then echo "Only enter a single value, good-bye!"
107: exit 1
108: fi
109:
110: shopt -s extglob # Allow extended pattern matching in bash
111: case "$NUM" in
112: +([0-9])) if [ "$NUM" -eq 0 ]
113: then echo "You must create at least one account, good-bye!"
114: exit 1
115: elif [ "$NUM" -gt 40 ]
116: then printf '%s' "$NUM are a lot of accounts, are you sure? "
117: read ANS
118: if [ "$ANS" != 'y' ]
119: then echo "Good-bye!"
120: exit 1
121: fi
122: fi
123: ;;
124: *) echo "You must enter a positive number, good-bye!"
125: exit 1
126: ;;
127: esac
128:
129: drawLine "Additional Group Setup"
130:
131: echo "An additional group can be created for each class, named"
132: echo "for the class prefix. Each student can be added as a member"
133: echo "of this group, so students will be members of two groups."
134: echo
135: printf '%s' "Do you wish to create an additional group per class (y/n)? "
136: read answer
137: if [ "$answer" = "y" ]
138: then
139: CREATE_ADDITIONAL_GRP="y"
140: echo
141: printf '%s' "Do you want to add yourself to this group as well (y/n)? "
142: read answer
143: if [ "$answer" = "y" ]
144: then
145: INSTRUCTOR=$(getInst)
146: ADD_INST_TO_ADDITIONAL_GRP="y"
147: fi
148: fi
149:
150: drawLine "Instructor Access"
151:
152: echo "The system umask of \"027\" allows full access by a file's"
153: echo "owner, read access by group members, and no access for others."
154: echo "By adding the instructor as a group member, for each student's"
155: echo "group (there is one group per account), the instructor is"
156: echo "given read access to all student files, facilitating grading and"
157: echo "support."
158: echo
159: echo "Configure accounts so the instructor has read access"
160: printf '%s' " to all created account home directories (y/n)? "
161: read answer
162: if [ "$answer" = "y" ]
163: then ADD_INST_TO_STUTENT_GRPS="y"
164: [ -z "$INSTRUCTOR" ] && INSTRUCTOR=$(getInst)
165: fi
166:
167: drawLine "Account Locking"
168:
169: TODAY=$(date +%D)
170: echo "For security accounts are created locked (disabled)."
171: printf '%s' "Enter the date the accounts should be unlocked (default: \"$TODAY\"): "
172: read UnlockDate JUNK
173: if [ -z "$UnlockDate" ]
174: then UnlockDate=$TODAY
175: fi
176: if [ -n "$JUNK" ]
177: then echo "You must enter a date such as \"mm/dd/yy\". Good-bye!"
178: exit 1
179: fi
180: CANONIZED_DATE=$(date --date="$UnlockDate" +%D) || exit 2
181:
182: # Check to make sure the date entered is not in the past:
183: Today_stamp=`date --date="$TODAY" +%s`
184: Canon_stamp=`date --date="$CANONIZED_DATE" +%s`
185: if [ "$Canon_stamp" -lt "$Today_stamp" ]
186: then
187: echo "You must enter today's date, or some date in the future!"
188: exit 2
189: fi
190:
191: if [ "$TODAY" = "$CANONIZED_DATE" ]
192: then pwPrefix=""
193: printf '\n%s\n' ' *** Accounts will be created unlocked (enabled)! ***'
194: else pwPrefix="!"
195: echo "Accounts will be unlocked at midnight on $CANONIZED_DATE."
196: fi
197:
198: drawLine "Initial Passwords"
199:
200: cat <<\EOF
201: For highest security, accounts can be created with default
202: passwords. If you chose this option the account names and
203: their passwords will be emailed to you. If not, accounts
204: will not have passwords (users are be forced to change the
205: passwords when they first login, regardless).
206: EOF
207: printf '\n%s' 'Create accounts with initial passwords? (y/n)'
208: read REPLY
209: case "$REPLY" in
210: [yY]*) GenPW=yes
211: echo
212: printf '%s' "Enter email address to send password report to: "
213: read INST_EMAIL JUNK
214: echo "Passwords will be sent to $INST_EMAIL"
215: ;;
216: *) GenPW=no
217: echo "No passwords will be generated!"
218: ;;
219: esac
220:
221: echo
222: if [ "$NUM" -lt 10 ]
223: then DISP_NUM="0$NUM"
224: else DISP_NUM="$NUM"
225: fi
226:
227: drawLine "Disk Quotas"
228:
229: QUOTA_SOFT=6000 # 6 MiB
230: QUOTA_HARD=12000 # 12 MiB
231: # note file quotas, and all quotas for /var and /tmp are not
232: # configurable in this script!
233:
234: echo Disk quotas are set for /home, /var, and /tmp for all users.
235: echo The defaults should be fine for most users but some may require
236: echo more space for their home directories.
237: echo
238: printf 'Use larger quotas for /home? (y/N) '
239: read answer
240: if [ "$answer" = "y" ]
241: then
242: QUOTA_SOFT=25000000 # 25 GiB
243: QUOTA_HARD=35000000 # 35 GiB
244: fi
245:
246: drawLine "Configuration Summary"
247:
248: # Show summary information for confirmation:
249:
250: if [ $# -gt 1 ]
251: then
252: printf '%s' "Creating $# classes of $((NUM + 1)) accounts each "
253: echo "(\"${1}00\" - \"${1}$DISP_NUM\", ...)."
254: else
255: printf '%s' "Creating 1 class of $((NUM + 1)) accounts "
256: echo "(\"${1}00\" - \"${1}$DISP_NUM\")."
257: fi
258:
259: echo "With quotas of"
260: printf '%-12s %-12s %-12s %-12s %-12s\n' \
261: 'Filesystem' 'Blocks-soft' 'Blocks-hard' 'Files-soft' 'Files-hard'
262: printf '%-12s %-12s %-12s %-12s %-12s\n' \
263: '/home' "$QUOTA_SOFT" "$QUOTA_HARD" 2000 3000
264: printf '%-12s %-12s %-12s %-12s %-12s\n' \
265: '/var' 1000 1500 100 200
266: printf '%-12s %-12s %-12s %-12s %-12s\n' \
267: '/tmp' 1000 1500 100 200
268:
269: if [ "$pwPrefix" = "" ]
270: then echo "Accounts will be created unlocked."
271: else echo "Accounts will be unlocked at midnight on $CANONIZED_DATE."
272: fi
273:
274: if [ "$GenPW" = "yes" ]
275: then echo "Password list will be emailed to \"$INST_EMAIL\"."
276: else echo "Accounts will NOT have initial passwords."
277: fi
278:
279: if [ "$CREATE_ADDITIONAL_GRP" = "y" ]
280: then echo "Creating an additional group per class."
281: if [ "$ADD_INST_TO_ADDITIONAL_GRP" = "y" ]
282: then echo " $INSTRUCTOR will be included in this group."
283: fi
284: fi
285:
286: if [ "$ADD_INST_TO_STUTENT_GRPS" = "y" ]
287: then echo "$INSTRUCTOR will have read access to all account files."
288: fi
289:
290: echo
291: printf "OK to proceed? (y/N)? "
292: read answer
293: if [ "$answer" != "y" ]
294: then
295: echo "You ARE the weakest link...good-bye!"
296: exit 1
297: fi
298:
299: # Create pipe to email message (password list):
300: [ "$GenPW" = "yes" ] && \
301: exec 3> >(/bin/mail -s "New $HOST Account Passwords" "$INST_EMAIL")
302:
303: for CLASS in $CLASSES
304: do
305: if grep "^$CLASS[0-9]" /etc/passwd >/dev/null 2>&1
306: then echo "Class \"$CLASS\" already exists, skipping..."
307: logger -p warn -t "add-users" \
308: "Skipping account creation for $CLASS, already exists."
309: continue
310: fi
311:
312: logger -t "add-users" "Creating new user accounts with prefix $CLASS."
313:
314: if [ "$CREATE_ADDITIONAL_GRP" = "y" ]
315: then echo "Creating new group \"$CLASS\"..."
316: groupadd "$CLASS"
317: if [ "$ADD_INST_TO_ADDITIONAL_GRP" = "y" ]
318: then
319: gpasswd -a "$INSTRUCTOR" "$CLASS"
320: fi
321: fi
322:
323: for i in $(seq -f "%02.0f" 0 $NUM)
324: do
325: User="$CLASS$i"
326: printf '%s' "Adding user $User."
327:
328: # Create group account for user:
329: groupadd "$User"
330: printf '.'
331:
332: if [ "$ADD_INST_TO_STUTENT_GRPS" = "y" ]
333: then
334: gpasswd -a "$INSTRUCTOR" "$User" |tr '\n' '.'
335: fi
336:
337: # Set the password: "" = no pw, unlocked, "!" = no pw, locked,
338: # "Xy1Abc" = pw and unlocked, and "!Xy1Abc" = pw, locked.
339: PASSWORD="$pwPrefix"
340: if [ "$GenPW" = "yes" ]
341: then
342: # pronouncable with at least one digit and capital leter:
343: pass=$(pwgen -cn1)
344: crypt=$(md5crypt "$pass")
345: PASSWORD="$pwPrefix$crypt"
346: printf "$User\t$pass\n" >&3 # Append name, password to email.
347: fi
348:
349: # Create user: -m means create home dir and copy /etc/skel,
350: # -p '!' means create NULL (no) password for Linux
351: # but initially lock (disable) the account,
352: # -g $User means put the user in his/her own group
353:
354: useradd -m -p "$PASSWORD" -g "$User" -K UMASK=027 "$User"
355: printf '.'
356:
357: # Add the user to group "tty". This is needed on some systems
358: # (such as Fedora 21) before users can use write, wall, talk:
359: gpasswd -a "$User" tty
360: printf '.'
361:
362: # This forces the password to be change every 150 days, and
363: # sets the date of last change to be 1/1/1970. Thus a user
364: # is forced to set a password the first time they log in, and
365: # that password should be valid for one semester (plus a few
366: # extra weeks).
367: chage -M 150 -d 1 "$User" #Linux cmd, Solaris uses passwd
368: printf '.'
369:
370: # Set the quotas for the user:
371: setquota -u "$User" $QUOTA_SOFT $QUOTA_HARD 2000 3000 /home
372: setquota -u "$User" 1000 1500 100 200 /var
373: setquota -u "$User" 1000 1500 100 200 /tmp
374: printf '.'
375:
376: # Add user to additional group:
377: if [ "$CREATE_ADDITIONAL_GRP" = "y" ]
378: then
379: gpasswd -a "$User" "$CLASS" >/dev/null
380: printf '.'
381: fi
382:
383: echo "done!"
384: done
385: done
386:
387: # Close the pipe to mail (which will send the email):
388: [ "$GenPW" = "yes" ] && exec 3>&-
389:
390: # Create an "at" job to unlock accounts on the specified date
391: # if necessary:
392: if [ "$pwPrefix" = '!' ]
393: then
394: at midnight $CANONIZED_DATE >/dev/null 2>&1 <<-EOF
395: for c in $CLASSES
396: do
397: for i in `seq -s ' ' -f "%02.0f" 0 $NUM`
398: do
399: usermod -U "\$c\$i"
400: done
401: done >/dev/null 2>&1
402: logger -t "add-users" "Enabling $CLASSES student accounts."
403: EOF
404: fi