#!/bin/bash #Copyright 2002, William Stearns #Released under the GPL. #Username must exist (single instance) in /etc/passwd and /etc/group . #Target UID and GID must not exist in /etc/passwd or /etc/group. #Race conditions galore. *sigh* Probably best to run this on a #system in single user mode. # #This will not change a user account _name_. # #To keep the same gid, specify the existing gid. Likewise with pid. if [ -f /usr/lib/samlib/samlib ]; then . /usr/lib/samlib/samlib else echo "/usr/lib/samlib/samlib is missing - please get it from" >&2 echo "http://www.pobox.com/~wstearns/" >&2 echo "Exiting." >&2 exit 1 fi for ONEFUNC in substline ; do if ! type $ONEFUNC >/dev/null 2>/dev/null ; then echo "Missing $ONEFUNC , please update samlib from" >&2 echo "http://www.pobox.com/~wstearns/" >&2 echo "Exiting." >&2 exit 1 fi done usage () { echo 'moveuser' >&2 echo ' Moves the specified user from its current user and group' >&2 echo 'numeric IDS to the specified numeric IDs.' >&2 echo '' >&2 echo 'Usage:' >&2 echo ' moveuser username new_UID new_GID [--dry-run]' >&2 echo 'Examples:' >&2 echo ' moveuser bparker 4005 4005 --dry-run' >&2 echo ' moveuser bparker 4005 --no-group' >&2 echo 'Exiting.' >&2 exit 1 } debug () { echo "$*" >&2 } #error () { # echo "$*" >&2 #} fail () { ExitVal=$1 shift echo "$*, exiting." >&2 exit $Exitval } CantFinish=1 SecurityProblem=2 if [ -z "$1" ]; then usage fi while [ -n "$1" ]; do case "$1" in -h|--help) usage ;; --dry-run) debug Dry run mode. DryRun='yes' ;; --no-group) debug Will ignore group id. NoGroup='yes' ;; *:*) fail $CantFinish Invalid parameter "$1" ;; [0-9]*) if [ -z "$NewUID" ]; then NewUID="$1" debug New UID is "$NewUID" else NewGID="$1" debug New GID is "$NewGID" fi ;; [A-Za-z]*) Username="$1" debug Username is "$Username" ;; *) debug Unrecognized parameter "$1" usage ;; esac shift done if [ "$EUID" != 0 ]; then SUDO="`which sudo`" if [ -z "$SUDO" ] && [ "$DryRun" != 'yes' ]; then fail $CantFinish 'You are not root, sudo is not available, and this is not a dry run' fi fi #No fair becoming root this way. :-) if [ "$NewUID" = 0 ]; then fail $SecurityProblem 'Unable to move user to UID 0' ; fi if [ "$NoGroup" != 'yes' ]; then if [ "$NewGID" = 0 ]; then fail $SecurityProblem 'Unable to move user to GID 0' ; fi fi if [ ! -f /etc/passwd ]; then fail $CantFinish 'missing /etc/passwd' ; fi if [ "$NoGroup" != 'yes' ]; then if [ ! -f /etc/group ]; then fail $CantFinish 'missing /etc/group' ; fi fi #Check if NewUID/NewGID already taken if [ `cat /etc/passwd | grep "^[^:]*:[^:]*:$NewUID:" | wc -l` -gt 0 ]; then fail $SecurityProblem "UID $NewUID is already taken" ; fi if [ "$NoGroup" != 'yes' ]; then if [ `cat /etc/group | grep "^[^:]*:[^:]*:$NewGID:" | wc -l` -gt 0 ]; then fail $SecurityProblem "GID $NewGID is already taken" ; fi fi case `cat /etc/passwd | grep "^$Username:" | wc -l | sed -e 's/ //g'` in 0) fail $CantFinish "No lines in /etc/passwd with name $Username" ;; 1) debug User account "$Username" found, good. ;; *) fail $SecurityProblem "Too many lines with account $Username in /etc/passwd" ;; esac if [ "$NoGroup" != 'yes' ]; then case `cat /etc/group | grep "^$Username:" | wc -l | sed -e 's/ //g'` in 0) fail $CantFinish "No lines in /etc/group with group $Username" ;; 1) debug Group "$Username" found, good. ;; *) fail $SecurityProblem "Too many lines with group $Username in /etc/group" ;; esac fi OldUID=`cat /etc/passwd | grep "^$Username:" | sed -e 's/^[^:]*:[^:]*://' -e 's/:.*//'` debug OldUID is "$OldUID" if [ "$NoGroup" != 'yes' ]; then OldGID=`cat /etc/passwd | grep "^$Username:" | sed -e 's/^[^:]*:[^:]*:[^:]*://' -e 's/:.*//'` debug OldGID is "$OldGID" fi #No fair moving root either. :-) if [ "$OldUID" = 0 ]; then fail $SecurityProblem "Unable to move root user" ; fi if [ "$NoGroup" != 'yes' ]; then if [ "$OldGID" = 0 ]; then fail $SecurityProblem "Unable to move GID 0" ; fi fi #Check if user logged in or any apps running under that userid LiveIDs=`find /proc -uid "$OldUID" -a -type d 2>/dev/null | sed -e 's@/fd$@@g' -e 's@/proc/@@g' | sort | uniq` if [ -n "$LiveIDs" ]; then fail $CantFinish "There are tasks \($LiveIDs\) running under UID $OldUID. Please finish them and log that user out" fi if [ "$NoGroup" != 'yes' ]; then LiveIDs=`find /proc -gid "$OldGID" -a -type d 2>/dev/null | sed -e 's@/fd$@@g' -e 's@/proc/@@g' | sort | uniq` if [ -n "$LiveIDs" ]; then fail $CantFinish "There are tasks \($LiveIDs\) running under GID $OldGID. Please finish them and log that user out" fi fi echo "$Username is not logged in, and no apps are running under that UID or GID." >&2 echo "It is your responsibility to make sure that this continues to be the case." >&2 echo "Ready to go? Press Enter if ready, Ctrl-C if not." >&2 read JUNK if [ "$OldUID" != "$NewUID" ]; then debug Changing UID #Check to see if any files are owned by target UID. #Sorry for the time this takes. Tough. TargetUIDFiles="`$SUDO find / -uid $NewUID`" if [ -n "$TargetUIDFiles" ]; then echo "There are files with the new UID ($NewUID). Please move these out of the way and restart." >&2 echo $TargetUIDFiles >&2 fail $SecurityProblem 'Target UID used by existing files' fi if [ "$DryRun" = 'yes' ]; then debug Would have run: "$SUDO find / -uid $OldUID | $SUDO xargs chown --from=$OldUID $NewUID" debug Would have run: "substline /etc/passwd \"\(^$Username:[^:]*\):$OldUID:\(.*\)\" \"\1:$NewUID:\2\"" else $SUDO find / -uid $OldUID | $SUDO xargs chown --from=$OldUID $NewUID || fail $CantFinish 'Chown uid failure' substline /etc/passwd "\(^$Username:[^:]*\):$OldUID:\(.*\)" "\1:$NewUID:\2" || fail $CantFinish 'Change uid in passwd failure' fi else debug Old and New UIDs are the same, no change needed. fi if [ "$NoGroup" != 'yes' ] && [ "$OldGID" != "$NewGID" ]; then debug Changing GID #Check to see if any files are owned by target GID. TargetGIDFiles="`$SUDO find / -gid $NewGID`" if [ -n "$TargetGIDFiles" ]; then echo "There are files with the new GID ($NewGID). Please move these out of the way and restart." >&2 echo $TargetGIDFiles >&2 fail $SecurityProblem 'Target GID used by existing files' fi if [ "$DryRun" = 'yes' ]; then debug Would have run: "$SUDO find / -gid $OldGID | $SUDO xargs chown --from=:$OldGID :$NewGID" debug Would have run: "substline /etc/passwd \"\(^$Username:[^:]*:[^:]*\):$OldGID:\(.*\)\" \"\1:$NewGID:\2\"" debug Would have run: "substline /etc/group \"\(^$Username:[^:]*\):$OldGID:\(.*\)\" \"\1:$NewGID:\2\"" else $SUDO find / -gid $OldGID | $SUDO xargs chown --from=:$OldGID :$NewGID || fail $CantFinish 'Chown gid failure' substline /etc/passwd "\(^$Username:[^:]*:[^:]*\):$OldGID:\(.*\)" "\1:$NewGID:\2" || fail $CantFinish 'Change group in passwd failure' substline /etc/group "\(^$Username:[^:]*\):$OldGID:\(.*\)" "\1:$NewGID:\2" || fail $CantFinish 'Change group in group failure' fi else debug Old and New GIDs are the same, no change needed. fi exit 0