Showing posts with label delete. Show all posts
Showing posts with label delete. Show all posts

Wednesday, January 28, 2009

Old School Keyboard Control and HTML Character Entities On Linux And Unix

Hey There,

I think the sickness is finally leaving my body (I'm referring to the virus, or whatever I have, and not the mental condition that I fear I'm going to be stuck with until they tag my toe ;), so, in between bouts of feeling like I'm not getting better, I'm cranking out this disjointed post. It's going to be a 2 point post, but there's absolutely no natural flow between the first and second topics. Basically, it goes against everything I was taught in school, but I'll be damned if "the man" is gonna keep me down any longer ;)

1. Stopping people from rebooting your Linux box with control-alt-delete and giving them something to think about.

This is pretty old-school (and, probably, old-hat) but I've never written about it before. Basically, what we want to do in this part of today's post is prevent folks with physical access to our Linux machines from rebooting them by hitting the control-alt-delete keys. Most people just disable this behaviour in /etc/inittab and use echo to issue a stern warning. I like to take it a bit too far ;)

a. Find the line in /etc/inittab that handles control-alt-delete functionality. It probably looks something like this:

ca::ctrlaltdel:/sbin/shutdown -r -t 4 now

Basically, we only need to worry about the last section (with sections being delimited by colons) that states the machine should execute "/sbin/shutdown -r -t 4 now" whenever someone presses the control-alt-delete key combination (ctrlaltdel in the file).

b. Now, edit this line so that no one can reboot the box using this key combo. Generally, this is how it's done:

ca::ctrlaltdel:/bin/echo "Control-Alt-Delete Key Combination Has Been Disabled"

and then run:

init q

or

init Q

to cause init to reread the /etc/inittab. This works very well, and is recommended for machines that need to meet certain availability requirements. If this is your personal machine, or a machine your group works on that isn't "important," try this change instead (followed by "init q" or "init Q")

ca::ctrlaltdel:/bin/crashland

And then make a little executable file that you can create by grabbing the worst errors out of your /var/log/messages log, or any other file, and adding a zinger at the end :)

host # chmod 755 /bin/crashland
host # cat /bin/crashland

#!/bin/sh

/bin/echo `date "+%b %d %H:%M:%S"` `hostname` kernel: rpcd[26560]: segfault at 00000000bafa1b44 rip 00000000555c3be9 rsp 0000000056e3ea90 error 4
/bin/echo `date "+%b %d %H:%M:%S"` `hostname` kernel: vcagentd[4968]: segfault at 0000000000000001 rip 000000005558d5d2 rsp 0000000056818858 error 4
/bin/echo `date "+%b %d %H:%M:%S"` `hostname` kernel: rpcd[16785]: segfault at 00000000bafa2b44 rip 00000000555c4be9 rsp 0000000056c3fa90 error 4
/bin/echo "This message has been repeated 537 times"
/bin/echo "Unrecoverable disk error on root disk. This partition may require"
/bin/echo "Emergency maintenance - please gracefully reboot if possible! - ctrl-alt-delete on your keyboard"


Oh, the fun ;)

2. Here's a little something goofy dealing with HTML character entities in general. It's a little exercise I started and then decided not to finish since it has no end that a human can reach without growing substantially older, slower and more bitter ;)

Enjoy the descent into meta-meta-meta-... Hell...



Testing Character Entities




printing < in HTML is actually typing &lt;


printing &lt; in HTML is actually typing &amp;lt;


printing &amp;lt; in HTML is actually typing &amp;amp;lt;


printing &amp;amp;lt; in HTML is actually typing &amp;amp;amp;lt;


printing &amp;amp;amp;lt; is becoming a much more apparent, never-ending, pain in the arse...




Hope everyone out there is staying healthy or, at least, not being sick ;)

Cheers,

, Mike




Discover the Free Ebook that shows you how to make 100% commissions on ClickBank!



Please note that this blog accepts comments via email only. See our Mission And Policy Statement for further details.

Friday, October 24, 2008

Creating And Deleting Local Zones On Solaris 10 Unix

Hey There,

Today's post is the final post in our quick series on dealing with local zones on Solaris 10. If you want to check the previous entries out, finish this paragraph. If you don't (or already have), just skip past this one :) The previous posts in this series have dealt with already-created local zones and how to create new file systems in a local zone on Solaris 10, modify filesystems in existing local zones and remove file systems in a local zone.

Today's "how to" is going to cover the bookends of this mini-series; creating local zones in Solaris 10 and destroying them.

NOTE: This notation is fairly obvious in this post (it was more appropriate in the following three), but I'll include it for completeness' sake. You have to be in the global zone to create or destroy a new local zone. See what I mean? ;) Also, resource/storage pools, etc, are outside the scope of this series of posts. If you're curious to learn a bit more about that aspect, check out our older series of posts dealing with using storage pools in Solaris 10. The link is to the final post, but all the preceding posts are linked to on that page; much like they are on this one.

Now, we'll go, step by step, through creating a local zone (up through the final "installation" phase) and, subsequently, undoing all of our hard work by destroying it ;)

1. First of all, fire up your old friend zonecfg and we'll get the party started. For our examples, we'll call our new local zone "DING" to maintain consistency with our other posts and we'll call our pool "CLOCK" (ding dong, yeah, it's sappy ;) You'll notice that you define the zone name when you invoke zonecfg, although just doing that doesn't actually "create" it, like so:

host # zonecfg -z DING <-- This is going to give you an error, but you can safely ignore it. Basically, the error just indicates that you're trying to configure a zone that hasn't been created yet. That's o.k., because we're just about to :)
zonecfg:DING> create

2. Next, we'll set up all of the minimally necessary parts of this local zone (note that we've already checked that enough resources (disk, IP, etc) exist in order for us to be able to install our new zone):

zonecfg:DING> set zonepath=/zones/DING/
zonecfg:DING> set autoboot=true
<-- I would recommend leaving this as "false" until you know you're good, but the worst that can happen isn't really all that bad, since a local zone caught in an auto-boot-loop won't cause you the same headache a straight-up box with the same issue would.
zonecfg:DING> add net <-- Notice how this puts you in a sub-menu, which shows up in the prompt. This happens for most device configuration and is pretty helpful if you have to walk away from your build for a while and come back to a screen where someone's hit enter 500 times ;)
zonecfg:DING:net> set address=99.99.99.1
zonecfg:DING:net> set physical=bge0
zonecfg:DING:net> end
<-- and this, universally, takes us back down (or up, depending on how you look at it) a menu, so we end up back at
zonecfg:DING>

3. Then, with the process just about completed, we'll assign the zone to the "CLOCK" storage pool (outside the scope of these posts, but you can checkout our older 4-part post on working with storage pools). Once that's complete, we'll verify our zone and commit the configuration, like this:

zonecfg:DING> set pool=CLOCK
zonecfg:DING> verify
zonecfg:DING> commit
<-- This writes the configuration, which, up until this point, is held in-memory.
zonecfg:DING> exit (or ^D [Ctrl d])

4. The last step in creating a zone isn't entirely obvious. The first time I did this, I thought I was done when I completed step three. The zone was created, I'd assigned it all of the resources it required (and tweaked all of those) and then verified and committed the zone. Alas, I was wrong, there was still one last thing left to do.

The final act, in creating and enabling your new local zone is to "install" the zone with the zoneadm command. This will not only re-verify all of your zone's resources, check and see if your new zone will run on your system (or any other, for that matter), but also installs all the necessary files in the local zone's root filesystem and creates all mount points as necessary. It's very simple to run and very easy to deal with (Although it may take a little bit more time, even if your setup is good :) - Just run it, simply, like this:

host # zoneadm -z DING install
Preparing to install zone email-zone
...
Zone DING is initialized.


And that's, basically, all you need to do to set up a local zone :) Of course, you should run "zoneadm -z DING boot," zlogin, etc, to boot the zone up, login and run your own tests, just to be sure everything is the way you like it. If you need to make any adjustments, you can still use zonecfg to modify your zone's configuration (theoretically ad infinitum).

Now let's get to work ruining it all, by destroying our local zone ;) It's actually very simple and only includes one "real" step. We'll do the first part, just do to be polite, and shutdown the zone before we destroy it, like so:

host # zoneadm -z DING halt <-- Totally unnecessary if you're going to utterly destroy your zone anyway

Then, all we need to do is run one command to end it all:

host # zonecfg -z DING delete <-- You can also use the -F flag if it won't go away. Note, also, that no commit is necessary, as it was when we created the local zone.

And there we have it; the poignant story a local zone from birth to death ;)

Cheers,

, Mike




Please note that this blog accepts comments via email only. See our Mission And Policy Statement for further details.

Tuesday, August 12, 2008

Recovering Deleted Files By Inode Number In Linux And Unix

Hey there,

It should be noted, at the outset, that this post is limited in its scope. We're going to be looking at one particular way in which you can recover an accidentally deleted file on Linux or Unix ( Tested on RHEL3 and Solaris 8 ). If you ever want to scour a hard drive that you need to get lots of information back from, then (assuming that you quarantined it immediately upon noticing this and haven't written to it since) you should check out The Coroner's Toolkit. Specifically, you'll want to look at the "ils" or "icat" programs, and most probably the "grave-robber" application to recover as much of everything as possible.

In our post, we're going to look at one condition under which Unix and Linux operating systems can actually hold onto files, even after they've been deleted, so that you can recover them. Of course, you have to realize you've deleted something you wanted to keep (in most cases) immediately, and a fairly specific set of circumstances has to be in play.

Note that all demonstrations have been tested on Solaris and RedHat. We're using RedHat's output, but the differences between the two were minor enough that they didn't bear repeating.

First, we'll look at the controlled scenario: In this case, we're going to create a file. Then we're going to delete it, knowing that we want to retrieve it afterward. How many times does that happen "by accident"? ;)

host # echo "hi there" >>FILE
host # ls -lai
total 8
57794 drwxr-s--- 2 user group 4096 Aug 11 12:36 .
851795 drwxr-s--- 3 user group 4096 Aug 11 12:31 ..
57795 -rw-r----- 1 user group 9 Aug 11 12:36 FILE


Now that the file's created, we can cat it by referencing the inode number (gotten with "ls -lai" above) very easily

host # find . -inum 57795 -exec cat {} \;
hi there


Then we'll set ourselves up so that we can delete the file and still be able to get it back. In order for us to be able to retrieve the deleted file later, we'll need to associate it with a filehandle. One easy way to do that is to run a tail (or similar command) on it):

host # tail -f FILE &
[1] 4741


and then we'll delete it:

host # rm FILE
host # ls -lai
total 8
57794 drwxr-s--- 2 user group 4096 Aug 11 12:41 .
851795 drwxr-s--- 3 user group 4096 Aug 11 12:31 ..


So, now it's gone. But, and this is the only thing that's saving us a whole lot of headache, the file is still open in memory since we still have a "tail -f" job running in the background. This means that the tail command still has a filehandle open for our file FILE. Of course, we can't refer to it by that name anymore. One interesting thing to note about an inode is that it contains virtually all the information you ever wanted to know about your file... except it's name! :)

Therefore, the following query with lsof fails to produce results:

host # lsof|grep FILE

IMPORTANT NOTE: If you have followed this process closely and either wrote down or remembered the "inode number" of the FILE file before we deleted it, you can skip all of this lsof stuff. Jump straight to the next IMPORTANT NOTE in this post :)

A quick look at what pseudo-terminal we're using, coupled with the knowledge that we ran "tail -f" on the file, makes for a pretty tidy grep string (of course, you don't need to have this much information. You can do this without of a filter and just have more lsof output to muck through):

host # tty
/dev/pts/0
host # lsof|grep pts/0|grep tail
tail 4741 user 0u CHR 136,0 2 /dev/pts/0
tail 4741 user 1u CHR 136,0 2 /dev/pts/0
tail 4741 user 2u CHR 136,0 2 /dev/pts/0


Now we know the PID (second column in from the left) of the process that still has the file open and we can use lsof to drill down even further, using the output of the pwd command to whittle down the output:

host # pwd
/home/users/user
host # lsof -p 4741|grep home/users/user
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
tail 4741 user cwd DIR 0,22 4096 57794 /home/users/user (host:/remote/users)
tail 4741 user 3r REG 0,22 9 57795 /home/users/user/FILE (host:/remote/users)


IMPORTANT NOTE: Welcome back, if you already knew the inode number, and "on we go" to everyone! One interesting thing to note about the lsof output is that there is only a "read" filehandle open for FILE. This is normal, since we're doing a "tail -f" and there doesn't need to be a write or read/write file descriptor active.

Now we can verify that the our "file" still exists by accessing it via the inode number:

host # find . -inum 57795 -exec cat {} \;
hi there


And, it's still there :) In order to preserve it (since it the inode-fd connection will be severed as soon as "tail -f" quits), we'll use similar "find" syntax to copy the inode to a filename and verify it, like so:

host # find . -inum 57795 -exec cp {} FILE.recovered \;
host # ls -lai
total 8
57794 drwxr-s--- 2 user group 4096 Aug 11 12:42 .
851795 drwxr-s--- 3 user group 4096 Aug 11 12:31 ..
57796 -rw-r----- 1 user group 9 Aug 11 12:42 FILE.recovered
host # cat FILE.recovered
hi there


Now we can quit our backgrounded "tail -f" job and not worry about it.

host # kill %%
host #
[1] + Terminated tail -f FILE


Like I said, a neat trick, but will only work if you get lucky. Pardon the double-cliche, but keeping calm when you realize that you may have royally screwed the pooch can go a long way toward keeping you from having to take the long way home (isolating the disk, scouring it with forensics tools, perhaps grepping the raw filesystem and manually reconstructing, etc).

The one thing to remember is that, if you delete a file on Linux or Unix, as long as one other process, that was using it when it existed, is still up and running after you delete it, there's a possibility that you can get your file back fairly quickly (actual results may vary. The possible scenario's are many and varied).

And, to give us a fair sendoff, here's a quick look at what you can expect if you delete a file and no process is attached to it and/or has a filehandle open against it (If you don't like watching train-wrecks, then I'll bid a good evening to you right here and now - cheers :)

host # ls -lai
total 8
57794 drwxr-s--- 2 user group 4096 Aug 11 12:43 .
851795 drwxr-s--- 3 user group 4096 Aug 11 12:31 ..
57796 -rw-r----- 1 user group 9 Aug 11 12:42 FILE
host # find . -inum 57796 -exec cat {} \;
hi there
host # rm FILE
host # find . -inum 57796 -exec cat {} \;
host # tty
/dev/pts/0
host # lsof|grep pts/0
ksh 3907 user 1u CHR 136,0 2 /dev/pts/0
ksh 3907 user 2u CHR 136,0 2 /dev/pts/0
ksh 3907 user 11u CHR 136,0 2 /dev/pts/0
lsof 5062 user 0u CHR 136,0 2 /dev/pts/0
lsof 5062 user 2u CHR 136,0 2 /dev/pts/0
grep 5063 user 2u CHR 136,0 2 /dev/pts/0
grep 5064 user 1u CHR 136,0 2 /dev/pts/0
grep 5064 user 2u CHR 136,0 2 /dev/pts/0
host # lsof|grep FILE
host # lsof|grep 57796
host # ls -lai
total 8
57794 drwxr-s--- 2 user group 4096 Aug 11 12:44 .
851795 drwxr-s--- 3 user group 4096 Aug 11 12:31 ..


...bummer.

, Mike




Please note that this blog accepts comments via email only. See our Mission And Policy Statement for further details.

Friday, February 8, 2008

Simple Linux Mods For Shell Script To Disable User Accounts

Hey there,

Yesterday, we posted a huge chunk of code getting the latest version our script to disable or delete user accounts. So, in the interest of keeping this page from becoming way too long, I'm just going to post the "diff" today. I quote the term diff, because I'm using it very loosely ;)

For the astute reader, I also noted a few missing single ticks on two lines in yesterday's script and have modified that post. Strangely enough, after repeated testing to make sure I was still all there, Solaris Unix doesn't seem to care (???) RedHat Linux picked it up right away though!

The modifications necessary to make the manual part of this version of our user disabling/deleting shell script are actually quite refreshing. While Solaris still insists that you unpack the utmp struct in /var/adm/wtmpx in order to get any year information from login monitoring commands, Linux provides this information for you with the "lastlog" command.

Yes, the modification is really that simple :)

So, without further ado, I've laid out the chunks of old code from yesterday, followed by the uncommented code that you should replace it with when running this on Linux. I put the two different places you'll need to make a change, in order, from top to bottom, as that seems normal to me ;)

You'll notice that the script's output is slightly different (and actually more conversational in tone), but, for our purposes, it doesn't need to be modified. We're still at the stage where we want to do a visual double-check.

Enjoy,


Creative Commons License


This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License

*** This section was in yesterday's script, but I've
*** changed the RedHat variable to be the lastlog
*** program instead of the /var/log/wtmp file

# $wtmpx_file_loc = "/var/adm/wtmpx"; # For Solaris
$wtmpx_file_loc = "/usr/bin/lastlog"; # For RedHat

*** The only other difference is a big chunk of code, but
*** actually a much simpler solution to implement in Linux

*** The Old Solaris Code - You should delete, or comment
* out this entire section and replace it with the new
* code below on Linux systems
*
* $template = "A32 A4 A32 l s s2 x2 l2 l x20 s A257 x";
* $recordsize = length(pack($template,( )));
* open(WTMP,$wtmpx_file_loc) or die "Unable to open wtmpx:$!\n";
* while (read(WTMP,$record,$recordsize)) {
* if ( $wtmpx_counter == 1 ) {
* print ".";
* $wtmpx_counter = 500;
* $wtmpx_total++;
* }
* ($ut_user,$ut_id,$ut_line,$ut_pid,$ut_type,$ut_e_termination,
* $ut_e_exit,$tv_sec,$tv_usec,$ut_session,$ut_syslen,$ut_host)=
* unpack($template,$record);
* push (@wtmpx_uv, "$ut_user ".scalar localtime($tv_sec));
* $wtmpx_counter--;
* $wtmpx_total++;
* }
*
*** End of the old Solaris code you should comment out or delete

* The substitute code for your Linux Shell Script
*
open(WTMP, "lastlog|") or die "can't open $wtmpx_file_loc: $!";
@rhwtmp = <WTMP>;
close(WTMP);
foreach $rhwtmp (@rhwtmp) {
if ( $wtmpx_counter == 1 ) {
print ".";
$wtmpx_counter = 500;
$wtmpx_total++;
}
push (@wtmpx_uv, $rhwtmp);
$wtmpx_counter--;
$wtmpx_total++;
}
*
* End of the substitute code - a lot easier to understand.


, Mike




Thursday, February 7, 2008

Shell Script To Disable and Delete User Accounts - Follow Up

Hey there,

This is a long overdue follow up to an older post where we began walking through the process of developing a decent and useful shell script, from scratch, to identify user accounts to disable or terminate based on the amount of time passed since they last logged in. Our goal was to make it simple to use and workable on RedHat Linux and Solaris Unix.

The rules are still the same: 45 days without a login equals account password locking and 90 days without a login equals account deletion. All actions (the password locking and account deletion) are still being kept manual, since the script isn't finished yet. You could argue that, no matter how good the script gets, that kind of stuff should always be kept manual, just in case. Don't want to delete the wrong person's account ;)

In this update, we've integrated another cross-check, utilizing Perl to get relevant information from the system wtmpx file (Referenced in greater detail in this post on taking a look inside wtmpx). We'll need to use this to provide a comparison against the modification time checks we're already doing on the users' home directories.

Important Note: Since this script is for both Linux and Unix, I made the location of the wtmpx file a variable, since it resides in /var/adm on Solaris and /var/log on RedHat Linux. The variable (comment out the one you need to) $wtmpx_file_loc has been added to address that issue. There are still other issues with Linux's implementation of wtmp that we will look at tomorrow (So that this script will run correctly on that OS. Please, no complaints until then ;)

Neither check is guaranteed to give a correct result, but utilizing both of them will quickly point out accounts flagged for deletion or lockdown that actually shouldn't be! In certain circumstances, both checks will fail to give entirely definitive results (for instance, if a user's home directory is over 90 days old and he/she doesn't have any entries in wtmpx, it's possible that we'd be deleting or locking the account in error, but much less likely).

Note that this is still a work in progress since, right now, we've got it printing out information to the screen for manual verification. In our next update, we'll take the wtmpx output (crucial to use Perl for this because we need the "year" and commands like "last" and "logins" don't provide that information) and convert it, so that we can have the "time-since-last-login" it provides to directly cross-reference against the filesystem checks inside the script; thus removing that manual part of the verification.

We've also made the user notifications a bit more warm and cuddly. Always in the best interest of everyone to be courteous when we can :)


Cheers,


Creative Commons License


This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License

#!/usr/bin/perl

#
# accountminder.pl
#
# 2008 - Mike Golvach - eggi@comcast.net
#
# Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License
#

$hostname=`hostname`;
$wtmpx_counter = 500;
$wtmpx_total = 0;
$wtmpx_file_loc = "/var/adm/wtmpx"; # For Solaris
# $wtmpx_file_loc = "/var/log/wtmp"; # For RedHat
print "PROCESSING WTMPX FILE FOR CROSS-CHECKING: ";
$| = 1;
$template = "A32 A4 A32 l s s2 x2 l2 l x20 s A257 x";
$recordsize = length(pack($template,( )));
open(WTMP,$wtmpx_file_loc) or die "Unable to open wtmpx:$!\n";
while (read(WTMP,$record,$recordsize)) {
if ( $wtmpx_counter == 1 ) {
print ".";
$wtmpx_counter = 500;
$wtmpx_total++;
}
($ut_user,$ut_id,$ut_line,$ut_pid,$ut_type,$ut_e_termination,
$ut_e_exit,$tv_sec,$tv_usec,$ut_session,$ut_syslen,$ut_host)=
unpack($template,$record);
push (@wtmpx_uv, "$ut_user ".scalar localtime($tv_sec));
$wtmpx_counter--;
$wtmpx_total++;
}
close(WTMP);
print ".\n";
print "PROCESSED $wtmpx_total ENTRIES\n";

$today=time();

@no_shells=`egrep -i 'nologin|false' /etc/passwd|awk -F":" '{print \$1}'`;
@in_admin=`grep -w 99999 /etc/passwd|awk -F":" '{print \$1}'`;
@user_names=`awk -F":" '{print \$1}' /etc/passwd`;
open(LOCKEDTEXT, "<locked.txt");
@locked_text = <LOCKEDTEXT>;
close(LOCKEDTEST);
print "\nRemoving LOCKED ACCOUNTS ";
foreach $locked_user (@user_names) {
print ".";
chomp($locked_user);
if ( grep /$locked_user/,@locked_text ) {
push(@tmp_user_names,$locked_user);
print "$locked_user ";
}
}
print "\n";
print "\nRemoving NO_SHELL ACCOUNTS ";
foreach $locked_user (@user_names) {
print ".";
chomp($locked_user);
if ( grep /$locked_user/,@no_shells ) {
push(@tmp_user_names,$locked_user);
print "$locked_user ";
}
}
print "\n";
print "\nRemoving ADMINISTRATIVE ACCOUNTS ";
foreach $locked_user (@user_names) {
print ".";
chomp($locked_user);
if ( grep /$locked_user/,@in_admin ) {
push(@tmp_user_names,$locked_user);
print "$locked_user ";
}
}
print "\n\n";
@total = @both = @unique_names = ();
%count = ();
foreach $possible_dupe (@user_names, @tmp_user_names) {
$count{$possible_dupe}++
}
foreach $possible_dupe (keys %count) {
push @total, $possible_dupe;
push @{ $count{$possible_dupe} > 1 ? \@both : \@unique_names }, $possible_dupe;
}
@user_names = @unique_names;
foreach $diruser (@user_names) {
$filer = `grep $diruser /etc/passwd|awk -F":" '{print \$6}'`;
push(@user_dirs,$filer);
}
print "FIRST RUN - Checking Home Directories...\n\n";
foreach $history_file (@user_dirs) {
chomp($history_file);
chomp($user_name = $user_names[$counter]);
print "${user_name}: ";
$counter++;
if ( -d "$history_file" ) {
@file_dates=stat("$history_file");
$file_date=$file_dates[9];
} else {
$file_date=0;
}
$difference=$today - $file_date;
if ( $difference >= 3888000 && $difference < 7776000 ) {
# print "45 days old or older, but under 90\n";
# print "passwd -l would be executed\n";
$| = 1;
print "Lock Em Up!\n";
print "Verifying Last Login: ";
$wtmpx_user = grep(/$user_name/, @wtmpx_uv);
if ( $wtmpx_user == 0 ) {
print "No Entries in $wtmpx_file_loc\n";
} else {
@wtmpx_user = grep(/$user_name/, @wtmpx_uv);
$verified = $wtmpx_user[$#wtmpx_user];
print "$verified\n";
}
push(@broken, $user_name);
open(SENDMAIL, "|/usr/lib/sendmail -t");
print SENDMAIL "From: System Administration\n";
print SENDMAIL "Reply-To: sec_account\@xyz.com\n";
print SENDMAIL "To: $user_name@\xyz.com\n";
print SENDMAIL "Subject: $user_name Account LockDown Notice\n";
print SENDMAIL "$user_name,\n";
print SENDMAIL "\n";
print SENDMAIL "\tWe found that you haven't logged into your account on unix server $hostname within the last 45 days.\n";
print SENDMAIL "\n";
print SENDMAIL "\tYour account has been locked. If you still require this access, please have your unix password reset.\n";
print SENDMAIL "\n";
print SENDMAIL "\tIf your account remains dormant for 90 days, your account will be deleted.\n";
print SENDMAIL "\n";
print SENDMAIL "Thanks,\n";
print SENDMAIL "\tSystem Administration\n";
close(SENDMAIL);
} elsif ( $difference >= 7776000 ) {
push(@buckwheats, $user_name);
$| = 1;
# print "User needs to be deleted - ";
# print "90 days old or older\n";
print "Delete Em!\n";
print "Verifying Last Login: ";
$wtmpx_user = grep(/$user_name/, @wtmpx_uv);
if ( $wtmpx_user == 0 ) {
print "No Entries in $wtmpx_file_loc\n";
} else {
@wtmpx_user = grep(/$user_name/, @wtmpx_uv);
$verified = $wtmpx_user[$#wtmpx_user];
print "$verified\n";
}
open(SENDMAIL, "|/usr/lib/sendmail -t");
print SENDMAIL "From: System Administration\n";
print SENDMAIL "Reply-To: sec_account\@xyz.com\n";
print SENDMAIL "To: $user_name@\xyz.com\n";
print SENDMAIL "Subject: $user_name Account Termination Notice\n";
print SENDMAIL "$user_name,\n";
print SENDMAIL "\n";
print SENDMAIL "\tWe found that you haven't logged into your account on unix server $hostname within the last 90 days.\n";
print SENDMAIL "\n";
print SENDMAIL "\tYour account has been terminated. If you still require this access, please send email to the system admin team.\n";
print SENDMAIL "\n";
print SENDMAIL "Thanks,\n";
print SENDMAIL "\tSystem Administration\n";
close(SENDMAIL);
} else {
print "OK\n";
}
}


, Mike




Monday, December 3, 2007

Finding User Accounts To Disable - Follow-Up Post

Hey there,

This is a follow up to a previous post where we're walking through the process of developing a decent script, from scratch; examining how to figure out what user accounts you could disable or delete (times vary, depending on your company's policy) on any particular box you have to administrate.

Today I've taken it a step farther. At this point the below script will check the modification time of all users listed in /etc/passwd and compare it against the current Unix time. If the mtime on the home directory is over 45 days and less than 90, we warn to disable (actual code not included) and if it's over 90 days, we warn to delete (same "no code" policy for that ;). If the time on the home directory is under 45 days, the account is consider active and OK.

Note that I found a few incorrect things and changed them, as I augmented this script; so this isn't just a bigger version of the original. First of all, it seems that the user's home directory is just as reliable as the existence of a history file. Also, it's much more likely to exist ;) Also, by verifying, I noticed that the mtime (Last Modification Time) was more reliable than the atime (Last Access Time) that we used before. That seems a bit counter-intuitive, but so far, it's proving out.

We've also added some extra functionality to remove users that we don't want to check. The first group are in a file we've called locked.txt, the second group are users without a valid shell and the final group are users who are in a particular group (like administrators who need accounts on all boxes, but might not login to any particular one for a long time).

Hope this script helps, at least, to point a few people in the right direction. This is a work in progress since it still contains several flaws, including no cross-referencing of possibly-available data that the system can provide with "last" and other such commands, as well as still having to deal with "touch."

Note that, on the lines ---> @locked_text = ###LOCKEDTEXT###;
and
open(LOCKEDTEXT, "###locked.txt");

the ### space-holders should be greater than and equal signs <>, in the first instance, and a < sign in the second, but the blog software interprets them as a unclosed and unallowed tags!

Have fun :)


Creative Commons License


This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License

#!/bin/perl

#
# 2007 - Mike Golvach - eggi@comcast.net
#
# Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License
#
# switching to mtime 9 - from atime - 8
#
# We don't need to check for errors on the command line since
# we won't be accepting any arguments
#

if ( -f "moreinfo") {
print "Removing old temporary file \"moreinfo\"\n";
unlink("moreinfo");
}

$today=time();

@no_shells=`egrep -i 'nologin|false' /etc/passwd|awk -F":" '{print \$1}`;
@in_unixtech=`grep 1621 /etc/passwd|awk -F":" '{print \$1}`;
@user_names=`awk -F":" '{print \$1}' /etc/passwd`;
open(LOCKEDTEXT, "###locked.txt");

@locked_text = ###LOCKEDTEXT###;
close(LOCKEDTEST);
print "\nRemoving LOCKED ACCOUNTS ";
foreach $locked_user (@user_names) {
print ".";
chomp($locked_user);
if ( grep /$locked_user/,@locked_text ) {
push(@tmp_user_names,$locked_user);
print "$locked_user ";
}
}
print "\n";
print "\nRemoving NO_SHELL ACCOUNTS ";
foreach $locked_user (@user_names) {
print ".";
chomp($locked_user);
if ( grep /$locked_user/,@no_shells ) {
push(@tmp_user_names,$locked_user);
print "$locked_user ";
}
}
print "\n";
print "\nRemoving UNIXTECH ACCOUNTS ";
foreach $locked_user (@user_names) {
print ".";
chomp($locked_user);
if ( grep /$locked_user/,@in_unixtech ) {
push(@tmp_user_names,$locked_user);
print "$locked_user ";
}
}
print "\n\n";
@total = @both = @unique_names = ();
%count = ();
foreach $possible_dupe (@user_names, @tmp_user_names) {
$count{$possible_dupe}++
}
foreach $possible_dupe (keys %count) {
push @total, $possible_dupe;
push @{ $count{$possible_dupe} > 1 ? \@both : \@unique_names }, $possible_dupe;
}
@user_names = @unique_names;
foreach $diruser (@user_names) {
$filer = `grep $diruser /etc/passwd|awk -F":" '{print \$6}'`;
push(@user_dirs,$filer);
}
print "FIRST RUN - Checking Home Directories...\n\n";
foreach $history_file (@user_dirs) {
chomp($history_file);
chomp($user_name = $user_names[$counter]);
print "${user_name}: ";
$counter++;
if ( -d "$history_file" ) {
@file_dates=stat("$history_file");
$file_date=$file_dates[9];
} else {
$file_date=0;
}
$difference=$today - $file_date;
if ( $difference >= 3888000 && $difference < 7776000 ) {
print "User needs to be disabled - ";
print "45 days old or older, but under 90\n";
# YOUR CUSTOM CODE HERE - "passwd -l" would probably suffice"
} elsif ( $difference >= 7776000 ) {
print "User needs to be deleted - ";
print "90 days old or older\n";
# YOUR CUSTOM CODE HERE -
} else {
print "OK\n";
}
}


, Mike





Saturday, December 1, 2007

Disabling and Removing Old User Accounts

Depending upon the size of your network, keeping track of user accounts on all of your Unix and Linux boxes can range from fairly simple to unbelievably hard to keep track of. I've noticed on the internet boards that lots of people are looking for the "one" way to semi-automate determining if user accounts need to be disabled, or if they're so old they need to be deleted.

I'm not a believer in the "one" solution theory. One of the things I love about Linux and Unix is, depending on how creative you're willing to be, there are probably 50 or more ways to do anything.

In any event I started looking at this problem, and noticed that almost everyone was using the "finger," "logins" or "last" commands to figure out how long it had been since someone logged in. In some extreme cases, I've even found examples where folks "pack" and "unpack" /var/adm/wtmpx with Perl to get the information. So, I naturally thought: Is it possible to do this without using the system's login-logging facilities?

The more I thought about this, the more I came to understand that both methods had their virtues and their faults. For instance, if /var/adm/wtmpx is missing, a lot of regular system accounting programs won't work. If you corrupt that file and remove /var/adm/lastlog (I'm using Solaris as my model - substitute file locations and names as necessary), "logins," "finger" and "last" don't report correctly unless they're reporting an error. The alternative approach also has its flaws. If, for instance, a user utilizes SSH to run a single command from a remote host using key-based passworless login, that shell won't be interactive and it won't update any shell history files!

Tackling this problem will take more than one post, so we'll start out with the core; treat this as a sort of tutorial on the phases that you go through from scratch to a decent script. Our assumption here will be that we have no access to any of the regular utilities like "last," "finger" and "logins." We'll use Perl to check out all users' .sh_history or .bash_history files (just covering sh, ksh and bash for now) and use the "stat" and "time" functions to determine how long it's been since the user last logged in.

Here's the core of the code, to get started. I've written it so it can be run on its own for now:


Creative Commons License


This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License

#!/bin/perl

#
# 2007 - Mike Golvach - eggi@comcast.net
#
# Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License
#
#
# We don't need to check for errors on the command line since
# we won't be accepting any arguments
#

$today=time();

@user_dirs=`awk -F":" '{print \$6}' /etc/passwd`;
foreach $history_file (@user_dirs) {
chomp($history_file);
print "${history_file}: ";
if ( -f "$history_file/.sh_history" ) {
@file_dates=stat("$history_file/.sh_history");
$sh_file_date=$file_dates[8];
} else {
$sh_file_date=0;
}
if ( -f "$history_file/.bash_history" ) {
@file_dates=stat("$history_file/.sh_history");
$bash_file_date=$file_dates[8];
} else {
$bash_file_date=0;
}
if ( $sh_file_date > $bash_file_date ) {
$file_date = $sh_file_date;
} elsif ( $bash_file_date > $sh_file_date ) {
$file_date = $bash_file_date;
} else {
print "Cannot find .bash_history or .sh_history for $history_file. Moving on!\n";
next;
}
$difference=$today - $file_date;
if ( $difference >= 3888000 && $difference < 7776000 ) {
print "User needs to be disabled- ";
print "45 days old or older, but under 90\n";
# YOUR CUSTOM CODE HERE - "passwd -l" would probably suffice"
} elsif ( $difference >= 7776000 ) {
print "User needs to be deleted - ";
print "90 days old or older\n";
# YOUR CUSTOM CODE HERE -
} else {
print "OK\n";
}
}


Within the next post or two, we'll follow up on this, so that we can add functionality to check system files, using system utilities, to verify that our initial information is correct. It's possible that someone just "touch"ed a user's history file and that access time information isn't necessarily correct. Also, as we noted above, folks can be using the host remotely, in a non-interactive shell, and never alter the access time of their history file.

For the time being, this code should be a good start, and something to think about and improve upon. Until next time :)

, Mike