Tuesday, October 6, 2009

Episode #63: Death To Users!

Tim kicks it off:

Last week we discussed ways to determine who is logged in to a system. Now what? Well, what is the most fun thing to do with that information? Of course, kick those users off the system.

Windows has two commands we can use, loggoff and rwinsta (Reset WINdows STAtion), and both do the same thing. Both commands require either a session name or session id, and both accept an /server option. How do we get the session name or id? Last week's post explained how to use qwinsta to get that info.

I didn't cover it last week, but there is another command, query session, that gives the same output as qwinsta. It has an undocumented switch /sm which returns the session id first which is easier for parsing. Unfortunately, it isn't available in XP so we will skip it.

C:\> qwinsta /server:Alpha
console shemp 0 Conn wdcon
rdp-tcp 65537 Listen rdpwd
rda-tcp#5 larry 1 Active wdica
rda-tcp#6 moe 2 Active wdica
curly 16 Disc wdica

We want to kick Larry and we have four ways to do it.

C:\> logoff /server:Alpha rda-rcp#5
C:\> logoff /server:Alpha 1
C:\> rwinsta /server:Alpha rda-rcp#5
C:\> rwinsta /server:Alpha 1

Why stop with just Larry, we want to kick off everyone! What if we try to logoff the listener?

C:\> logoff /server:Alpha rda-rcp
If you reset this session, all users using this protocol will be logged off,
Continue (n=no)? y
C:\> qwinsta /server:Alpha
console shemp 0 Conn wdcon
rdp-tcp 65537 Listen rdpwd
larry 1 Disc wdica
moe 2 Disc wdica
curly 16 Disc wdica

We disconnected the users, but we didn't kill their session. The behavior is different depending if a listener or a active/disconnected session is specified. Unfortunately, rwinsta acts the same way so that won't help. So then how do we kill the session? We will need to get the session id's and logoff each one.

C:\> for /F "tokens=2,3" %i in ('qwinsta /server:xen03 ^| findstr  "Active Disc"') do
@echo %i | findstr /v "[a-z]" && logoff /server:xen03 %i || logoff /server:xen03 %j

I'll just explain the differences since you can get most of the details from the last post. Previously, we worked with tokens 1 and 2 in order to find the username. This week we want token 2 or 3 in order to get the session id. Remember, a space is the default delimiter and is therefore ignored at the beginning of the line. The first token is either the session name or the username, the second token is either the username or session id.

Now let's look at the logic used to find the session id and ultimately logoff the user. Stealing from Episode 47 we use "the shorthand [command1] && [command2] || [command3]. If command1 succeeds, command2 will run. If command1 has an error, command3 will execute." In our example, command1 looks at the variable %i to ensure it does NOT contain a letter (and is therefore a number). If %i is determined to be a number (session id), then we use it to logoff the user, if %i is not a number then %j is our session id and is used to logoff the user.

So now we have everyone off the server. Now we can take it offline and install Windows 2008 R2 (unless you're Hal).

Hal weighs in:

Just for that crack, Tim, I'm not going to invite you to my Windows 7 release party...

In general, to kick a user off a Unix system you need to either kill their command shell or one of the ancestors of that process-- like the SSH daemon that spawned their shell or the X server that's supporting their windows. Back in Episode 22 I noted that the pkill command was very handy for this, so let's review:

# pkill -u hal         # kicks hal off the system by killing all hal-owned procs
# pkill -P 1 sshd # kicks all SSH logins off system, master daemon still running
# pkill X # terminate GUI session on console

The above commands are all fairly indiscriminate. For example, the first command kicks a single user off the system by terminating all processes owned by that user. This includes not only their command shells, but also any other jobs that user might have running. That might not be the best idea if the user had a legitimate but long-running job that shouldn't have been terminated.

However, pkill also lets us be more selective. For example, "pkill -u hal bash" would kill only the bash command shells running as user hal. Actually bash apparently traps SIGTERM, so we need to explicitly use SIGKILL:

# pkill -9 -u hal bash

The other, less discriminating versions of the pkill command I showed you earlier work without the "-9" because they're terminating ancestor processes of users' shells, which forces those shells to exit.

Another approach is to terminate only the processes associated with a login session on a particular tty. The who command will show us the tty associated with each logged in user, and we can use "pkill -t ..." to terminate only processes associated with that pty:

# who
moe pts/0 2009-10-03 07:51 (host1.deer-run.com)
larry pts/2 2009-10-03 07:51 (host2.deer-run.com)
hal pts/3 2009-10-03 07:52 (host3.deer-run.com)
# pkill -9 -t pts/3

By the way, the "-t" option is not unique to pkill: many other Unix commands allow you to get information for a single tty. For example, on older systems that don't have pkill I can use "ps -t ..." to do something similar:

# who
moe pts/0 2009-10-03 07:51 (host1.deer-run.com)
larry pts/2 2009-10-03 07:51 (host2.deer-run.com)
hal pts/3 2009-10-03 08:06 (host3.deer-run.com)
# ps -t pts/3
1511 pts/3 00:00:00 bash
# kill -9 1511

Similarly, the other pkill variants I've shown you in this Episode can be accomplished with a combination of ps, grep, and kill if you happen to have a Unix machine without pkill installed.