ProgrammingThis forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.
Notices
Welcome to LinuxQuestions.org, a friendly and active Linux Community.
You are currently viewing LQ as a guest. By joining our community you will have the ability to post topics, receive our newsletter, use the advanced search, subscribe to threads and access many other special features. Registration is quick, simple and absolutely free. Join our community today!
Note that registered members see fewer ads, and ContentLink is completely disabled once you log in.
If you have any problems with the registration process or your account login, please contact us. If you need to reset your password, click here.
Having a problem logging in? Please visit this page to clear all LQ-related cookies.
Get a virtual cloud desktop with the Linux distro that you want in less than five minutes with Shells! With over 10 pre-installed distros to choose from, the worry-free installation life is here! Whether you are a digital nomad or just looking for flexibility, Shells can put your Linux machine on the device that you want to use.
Exclusive for LQ members, get up to 45% off per month. Click here for more info.
I am trying to create a BASH script that will monitor users who login to a Linux machine locally. If they exceed the maximum number of logins their accounts will be locked and an email will be sent to the administrator. I've found applications that will do this but I want to see it in a BASH script to help me understand the process. I know I can use the faillog, at least that is what I am thinking, to track the say 5 unsuccessful logins. I just don't know how to take that information and lock them out and send an email to the admin. Any help would be greatly appreciated. I am really new to Linux mainly a Windows admin but trying to move off and into the Linux world because this OS is simply amazing. Thanks again.
Well, the lastb command can be used to list bad login attempts. The last command can be used to show successful attempts. You could try to parse these and extract the information that way.
A question arises... how do you want to limit the number of failed logins before the program locks the account? Would it be a certain number per day, or a certain number before a successful attempt, regardless of the date?
Is performance an issue? How many users are we talking here?
I want to track it by number of invalid attempts until successful attempt is made. In short, after 5 unsuccessful logins the user account is locked out and an email is sent to the administration stating Joe User's account is locked because he had 5 unsuccessful logins.
There are also a few things to watch out for... For example, you want the program to not lock the root user, or any other special-user accounts. If the program could do this, then a malicious user could lock you out of the machine just by trying to log in five times as root and getting the password wrong...
I'd probably want to separate out the three parts into separate functions, just to keep the code nice and tidy and understandable.
Shell scripts are read in a single pass, and so functions must be defined before they are used, so you cannot do something like this:
Code:
#!/bin/bash
my_function
another_function
my_function () {
# define function here
}
another_function () {
# define function here
}
You can't do that because at the time the shell calls the functions my_function and another_function, they are not defined.
However, defining long set of functions first, and then calling them all from the bottom of the script makes it a little hard to read the script and understand it - something any good programmer should keep in mind.
To improve readability, I like to formulate long scripts like this:
Code:
#!/bin/bash
main () {
# The main flow of the program goes here
some_function
}
some_function () {
# We can define this here because we _call_ main at the end of the file after this
}
main "$@"
This is just my preference, but it might help to explain why I structure the script as I do.
So, here goes, what I came up with. The email mechanism will need to be tailored to your system, depening on what command line mail tools you have available.
Code:
#!/bin/bash
# This variable holds the number of consecutive failed attempts allowed
# before the action is taken
FAILNUM=3
# Change this to send to whatever email address you like.
NOTIFY_ADDRESS=matthew@localhost
main () {
users_to_lock=$(get_list_users_to_lock)
for u in $users_to_lock; do
lock_user $u && users_locked="$users_locked $u"
done
if [ "$users_locked" != "" ]; then
notify_admin $users_locked
fi
}
get_list_users_to_lock () {
# list the failed login attempts and successful attempts
# with reformatting to make it possible to do a chronological
# sort using the program "sort".
# we will use a temporary file to store some stuff...
tmp=$(mktemp)
# The cut command is used to print only the user name and
# the login time
lastb | cut -c1-8,40-56 | while read user attempt_date
do
if [ "$user" = "" ]; then
break
fi
# use the date command to reformat the date string
echo "$user $(date -d "$attempt_date" "+%Y%m%d%H%M%S") FAILURE"
done > "$tmp"
last | cut -c1-8,40-56 | while read user attempt_date
do
if [ "$user" = "" ]; then
break
fi
echo "$user $(date -d "$attempt_date" "+%Y%m%d%H%M%S") SUCCESS"
done >> "$tmp"
# Now we have a temp file with the username, date and status of
# each login attempt. Sorting this file will group the results
# by user and then sort by time.
# we'll use this counter to check how many failures in a row we
# have found
consecutive_failures=0
# OK, do the sort and read in the values into some variables.
sort "$tmp" |grep -v "^reboot" |while read user date result; do
case $result in
SUCCESS)
consecutive_failures=0
;;
FAILURE)
let consecutive_failures+=1
;;
esac
if [ $consecutive_failures -gt $FAILNUM ]; then
echo $user
fi
done | sort -u
# don't forget to clean up after ourselves
rm -f "$tmp"
}
lock_user () {
# get the UID (numerical ID for user)
uid=$(grep "^$1:" /etc/passwd |cut -d: -f 3)
if [ $? -ne 0 ]; then
echo "UID lookup for user $1 failed" 1>&2
return 1
fi
case "$uid" in
[0-9][0-9]*)
# we want a numerical value... don't do anything in this case
echo nop > /dev/null
;;
*)
echo "uid is not numeric - $uid - weirdness"
return 1
;;
esac
if [ "$uid" -lt 1000 ]; then
echo "Will not lock system account: $1 (uid is $uid)"
return 2
fi
# Check if the account is already locked
# the password hash is prefixed with ! if the account is locked
# the password is in the /etc/shadow file, so we can only do
# this as root.
# anyhow, if we find that the user if
grep -q "^$1:\!" /etc/shadow
case $? in
0)
# We found this line - the user is already locked
echo "account for user $1 is already locked" 1>&2
return 3
;;
1)
# OK, the account is not already locked... lets do it
usermod -L $1 && return 0 || return 4
;;
*)
# The grep failed for some reason - probably permissions on the
# /etc/shadow
return 5
;;
esac
}
notify_admin () {
cat <<EOD | mailx -s "$# account(s) locked because of failed login attempts" $NOTIFY_ADDRESS
The following user accounts have been locked because of multiple
FAILED login attempts to host $(hostname):
$*
Enjoy
--
Your lovely script
EOD
}
main "$@"
The script would presumably be run from cron once every so often.
Now, the reasons not to use this script:
This is not really the right approach to take at all. Your login program should be doing this, and should do it is real time. If you run the script once every 24 hours, that's a lot of time for an attacker to attack your machine without you being notified.
There are already programs which do this sort of thing - don't re-invent the wheel.
Calling date many times - once per line of last and lastb output is slow and loads the machine more than is necessary. A better script would parse the date internally, and thus a better choice of language would be Perl of Python, both of which have modules which can access the wtmp directly, have good date parsing functions, and thus will be a lot more efficient. This is especially important on machines which are going to have large wtmp. They can get very big - do not underestimate this.
It is not thoroughly tested. Any program which automatically locks user accounts is dangerous, and you really shouldn't run it unless it's had serious testing.
If I hired a programmer to write it, and got this back, I would not hire them again - wrong choice of language.
DOS attack vector - anyone wanting to mess about another user can deliberately get their password wrong 5 times and have their account locked.
UIDs of less than 1000 are quite OK and used for some user accounts. 1000 is commonly used on main stream Linux distros, but it is not a given - therefore you have a maintenance and portability problem.
LinuxQuestions.org is looking for people interested in writing
Editorials, Articles, Reviews, and more. If you'd like to contribute
content, let us know.