login audit bash script

I am a bash beginner and I need to write an script to check my users login time. This has to be in a format of :

This script has to work on a server to check all the users. I know that I have to use "last" command but I have no idea how to do it.
any assistance is appreciated.
Thanks

There's a script in this thread

That does something similar to what you seem to need. You'll have to modify the output to meet your needs, and drop the 'last day' limitation (should actually make it less complicated); at the least it should give you an idea about how to use the last command to do what you want.

Great! Thanks very much

---------- Post updated at 01:47 PM ---------- Previous update was at 10:57 AM ----------

Well, apparently it doesn't work that way though. I need to get the files from /var/log with .bz2 extension and decompress them. this is where "last" command access and reads the wtmp file to get data when you use "finger" command.
The question is: without sudo or su permission how can I decompress thos bz2 files and put all of them together. because there are plenty of them in /var/log
Thanks.

Unless you have root privledges you cannot get this information. By design, a regular user cannot access this permission.

Really? Using last under linux, and the script below, I can generate exactly what you described in your original post. If you're running on FreeBSD, then the fields are organised differently, and last accepts different options, but the information is the same and can be parsed and presented without having to have root access.

#!/usr/bin/env ksh
last -Fa|awk '
    /wtmp begins/ { next; }
    /still logged in/ { next; }
    $0 == reboot { next; }

    NF > 0  {
        if( NR > 1 )
            printf( "\n" );

        printf( "       User:\t%s\n", $1 );     # user
        printf( "      Start:\t%s %s %s %s\n", $3, $4, $5, $6 );
        if( $9 == "down" )
            printf( "        End:\tshutdown\n" );
        else
            printf( "        End:\t%s %s %s %s\n", $9, $10, $11, $12 );

        if( substr( $NF, 1, 1 ) == "(" )
        {
            t = $NF;
            h = "localhost";
        }
        else
        {
            t = $(NF-1);
            h = $NF;
        }

        gsub( "[()]", "", t );
        printf( "    Time On:\t%s\n", t );
        printf( "Remote Host:\t%s\n", h );
} '

Sample output produced by the script:


       User:    scooter
      Start:    Tue Feb 14 22:33:16
        End:    Wed Feb 15 00:07:52
    Time On:    01:34
Remote Host:    localhost

       User:    scooter
      Start:    Sun Feb 12 12:52:49
        End:    Sun Feb 12 12:55:21
    Time On:    00:02
Remote Host:    liz

1 Like

I have 2 questions:
1- How did you learn bash scripting so well?
2- I have to look at the whole /var/log/wtmp files. There are so many of them and I have to add them to one file but I can't :frowning: this is what I did:

# Check for existing /tmp/tst folder
if [ "/tmp/tst" != "" ]; then
rm -rf /tmp/tst
# Make a directory in /tmp called tst
mkdir -p /tmp/tst
# Copy wtmp files in /tmp/tst folder and unzip them:
cp -f /var/log/wtmp* /tmp/tst/
fi
bzip2 -d /tmp/tst/wtmp-*.bz2 | last -f wtmp-* > wtmpfile
cat wtmpfile

Then I have to add all the logged time of my users together and give a total as output. In your script you just used "last" command. which gives you only information from present machine not all other servers on the network.
correct me if I am wrong....

Lots of practice. I've been writing code for a very long time now, and still learn something with nearly every post.

I see. I didn't grock that you needed to produce output based on all of them, and not just from the most recent.

I think you are on the right track. You might find this a bit easier to manage:

#!/usr/bin/env ksh

cd /tmp                     # safe place to work
tfile=/tmp/wtmp.$$          # temp file to uncompress into
big_file=/tmp/$USER.wtmp    #collect all output from wtmp into one file

ls /var/tmp/wtmp*bz2 | while read file    # for each wtmp file
do
    bunzip2 -dc $file >$tfile   # uncompress writing output to tmp file
    last -F -a -f $tfile        # run last on it
done >$big_file                 # save all output from last in one file
last -F -a >>$big_file          # append formatted output from current wtmp
rm $tfile                       # tmp file not needed

### parse your big file (/tmp/$USER.wtmp) here #####

# cleanup before exit
rm $big_file

This unzips each bzipped wtmp file directly to a temporary file, and then immediately runs last on it collecting all of the output in one file. A final last is executed which uses the current wtmp file and appends that output to the big file. You can then parse the big file as needed. You won't need a temporary directory because you don't have to copy the files.

Hope this helps.

This is what I have done so far with your help:

#!/bin/bash
if [ $# -ne 1 ]; then
    echo Usage: timeon username
    exit 1
fi
if [ $# -eq 1 ]
    then
        user=$1
#    else
#        user=$USER
fi
# check if the user exists on Matrix
a=$(ypcat passwd | grep -w ^$user)
if [ "$a" == "" ]
    then
        echo "$user does not exist on Matrix, try again." >&2
        exit 2
elif [ "$a" != "" ]; then
        # get the username
        username=$(echo $a | cut -d: -f1)
        echo Username:    $username
fi
# Check for existing /tmp/tst folder
if [ "/tmp/tst" != "" ]; then
rm -rf /tmp/tst
# Make a directory in /tmp called tst
mkdir -p /tmp/tst
fi
# Copy wtmp files in /tmp/tst folder and unzip them:

cd /tmp/tst                     # safe place to work
tfile=/tmp/tst/wtmp.$$          # temp file to uncompress into
big_file=/tmp/tst/$USER.wtmp    #collect all output from wtmp into one file

ls /var/log/wtmp*bz2 | while read file    # for each wtmp file
do
    bunzip2 -dc $file >$tfile   # uncompress writing output to tmp file
    last -F -a -f $tfile        # run last on it
done >$big_file                 # save all output from last in one file
last -F -a >>$big_file          # append formatted output from current wtmp
rm $tfile                       # tmp file not needed

### parse your big file (/tmp/$USER.wtmp) here #####

# cleanup before exit
#rm $big_file
b=$( cat /tmp/tst/$USER.wtmp | grep $user )
echo $b

but what I get as the output is some data that I don't know how to add them up for the followings:
Start Date: Sept 27, 2011 End Date : Jan 26, 2012 Time On : 31hrs 12min
I know that I have to use grep and bc but don't know how.
This is the example of my output:

You've got a good start. You're getting lots of 'jumbled' output because you are assigning it to a variable and that's messing with newlines. Try adding this to the end of your script instead of the last two lines (it's from earlier in the tread with the last command replaced with the grep):

# your script as you have it up to here....
# cleanup before exit
#rm $big_file

grep $user  /tmp/tst/$USER.wtmp | awk '
    /wtmp begins/ { next; }
    /still logged in/ { next; }
    $0 == reboot { next; }

    NF > 0  {
        if( NR > 1 )
            printf( "\n" );

        printf( "       User:\t%s\n", $1 );     # user
        printf( "      Start:\t%s %s %s %s\n", $3, $4, $5, $6 );
        if( $9 == "down" )
            printf( "        End:\tshutdown\n" );
        else
            printf( "        End:\t%s %s %s %s\n", $9, $10, $11, $12 );

        if( substr( $NF, 1, 1 ) == "(" )
        {
            t = $NF;
            h = "localhost";
        }
        else
        {
            t = $(NF-1);
            h = $NF;
        }

        gsub( "[()]", "", t );
        printf( "    Time On:\t%s\n", t );
        printf( "Remote Host:\t%s\n", h );
} '

This will format each entry from the huge wtmp output for the given user. If you need a total time, or a single entry with totals instead of one per login, then the awk will need to be changed or replaced with something you are more comfortable with. Adding this, and running it, I think will let you see how you need to go forward with the grep and processing the complete wtmp output.

Hope this helps/makes sense.

You are awesome! Thank You!
it is a fantastic piece of code but, I never worked with awk and it is getting too complicated for me. is there anyway you simplify this for me with use of grep? I need the:
Start Date
End Date
Time On in minutes

Like I said I am a beginner in scripting.:wink:

This might be a bit easier to grock:

grep $user /tmp/$USER.wtmp| while read  uname tty st_dow st_mon st_day st_time st_yr junk end_dow end_mon end_day end_time end_yr ttime host
do
    case $end_dow in
        crash|gone|down|still) ;;       # ignore those with missing info
        *)
            printf "\n"
            printf "       User:\t%s\n" $uname
            printf "      Start:\t%s %s %s %s\n" $st_dow $st_mon $st_day $st_time
            printf "        End:\t%s %s %s %s\n" $end_dow $end_mon $end_day $end_time
            printf "    Time On:\t%s\n" $ttime
            printf "Remote Host:\t%s\n" ${host:-localhost}
        ;;
    esac
done

How can I only get the latest date from the above loop? I tried to insert the result in a txt file and use sort but didn't work!

try an command pinky u dnt need script for such an job

I need a script. if you can help I appreciate it!

Yes, sorting the output would have been disaster!

You can try adding a sort after the grep in the pipeline. This will sort based on the start login time and write the most recent events first. In addition, add an exit after you print the first bit of data. This will allow any 'still logged in' records to be discarded, and will print the last complete session for the user.

grep $user /tmp/$USER.wtmp| sort  -k 7rn,7 -k 4rM,4 -k 5rn,5 -k 6rn,6  | while read  uname tty st_dow st_mon st_day st_time st_yr junk end_dow end_mon end_day end_time end_yr ttime host 
do
    case $end_dow in
        crash|gone|down|still) ;;       # ignore those with missing info
        *)
            printf "\n"
            printf "       User:\t%s\n" $uname
            printf "      Start:\t%s %s %s %s\n" $st_dow $st_mon $st_day $st_time
            printf "        End:\t%s %s %s %s\n" $end_dow $end_mon $end_day $end_time
            printf "    Time On:\t%s\n" $ttime
            printf "Remote Host:\t%s\n" ${host:-localhost}
            exit 0
        ;;
    esac
done

---------- Post updated at 13:29 ---------- Previous update was at 13:26 ----------

I forgot to mention that you might want to tighten up the sort a bit. It sorts based on year, month, day and hour, but doesn't take into account minutes, so if a user logged in and out twice in the same hour, you might not get the most recent one.

=================================================
I really appreciate your help. I changed the script so I can handle it with my own knowledge. Your script is very professional and I didn't understand the whole script. I attached the part that I did on my own here however, I need to calculate the TIME ON as well. I would like to use "bc". could you help me with that too?

leng=`expr length "$username"`
# if username length is more than 8 characters print an error.
if [ $leng -gt 8 ]; then
    new_username=$(echo $username | cut -c 1-8)
    else
    new_username=$username
fi
ls /var/log/wtmp*bz2 | while read file    # for each wtmp file
do
    bunzip2 -dc $file >$tfile    # uncompress writing output to tmp file
    last -F -a -f $tfile         # run last on it
    done >$big_file             # save all output from last in one file

last -F -a >>$big_file              # append formatted output from current wtmp

rm $tfile                           # tmp file not needed

echo $big_file | egrep $new_username /tmp/tst/$USER.wtmp > /tmp/tst/mytst

# The Start Date Calculation
st_dat=$(cat /tmp/tst/mytst | head -1 | cut -c 27-32)
st_dat1=$(cat /tmp/tst/mytst | head -1 | cut -c 43-46)
st_dat2=$(echo $st_dat", "$st_dat1)
echo Start Date:    $st_dat2    # THIS IS THE START DATE OUTPUT
# The End Date Calculation
end_dat=$(cat /tmp/tst/mytst | tail -1 | cut -c 53-59)
end_dat1=$(cat /tmp/tst/mytst | tail -1| cut -c 70-75 )
end_dat2=$(echo $end_dat", "$end_dat1)
echo End Date:    $end_dat2    # THIS IS THE START DATE OUTPUT

---------- Post updated at 04:41 PM ---------- Previous update was at 04:38 PM ----------

This is a sample of my output in a txt file that I need to calculate the TIME ON from:

The time on is the value in parens. In the example below, the user was logged in just over an hour:


myusername pts/0 Thu Jun 9 13:38:50 2011 - Thu Jun 9 14:50:32 2011 (01:11) 142.204.248.97

I don't think you need to compute anything, unless you need to sum the total time over all entries.

A couple of pointers:
You don't need to cat files into most commands. Certainly not head, grep and the like. You can use this form:

st_dat1=$(head -1 cat /tmp/tst/mytst | cut -c 43-46)

Head reads from the file named on the command line, so cat and pipe are unnecessary.

To 'join' the values of two variables together, you don't need echo:

st_dat2="$st_dat, $st_dat1"

Thanks very much. I fixed them. very insightful points. I am learning...:wink:
Yes, I need to add up my time and get a total. I posted my question here: