Sometimes it can be useful to get notified when someone logs into the system via SSH. In this post I am going to show several possible solutions.
For all solutions to work, we will need to have an utility that sends emails. I prefer mailx (from mailx package in RHEL-based distros or bsd-mailx / mailutils in Debian-based distros). Also, I assume that you use OpenSSH as a server; other SSH servers may have their own peculiarities.
Using PAM Authentication
This solution relies upon pam_exec module
Prerequisites: UsePAM yes in /etc/ssh/sshd_config.
First of all, we will need a script which sends a notification (let its name be /usr/local/bin/notify.sh). It can look something like this:
#!/bin/sh SENDER="[email protected]" RECIPIENT="[email protected]" if [ "$PAM_TYPE" != "close_session" ]; then host="$(hostname)" subject="SSH Login: $PAM_USER from $PAM_RHOST on $host" message="$(date) $(tail /var/log/auth.log) # /var/log/secure for RHEL-based $(who)" echo "$message" | mailx -r "$SENDER" -s "$subject" "$RECIPIENT" fi exit 0
The script needs to be executable: chmod 0755 /usr/local/bin/notify.sh
Then, we will need to add this line to /etc/pam.d/sshd:
session optional pam_exec.so /usr/local/bin/notify.sh
A brief explanation as to what the script does:
SENDERandRECIPIENTdefine the sender and recipient of the email;RECIPIENTis obviously the person who is going to receive these notifications;- Since
pam_execis configured to be used as a session module,PAM_TYPEvariable can be eitheropen_sessionorclose_session. Since we need to send notifications when a session is opened, we filter outclose_session; PAM_USERwill be the name of the authenticated user,PAM_RHOSTwill be the name of the remote (connecting) host. The value ofPAM_RHOSTdepends on several factors:- if
UseDNSis set tonoinsshd_config, the value will be the IP address of the remote host; - otherwise, if the remote IP passes the FCrDNS check, the value will match that of the PTR record of the remote host (and you can get the IP with the help of
gethostbyname()or a similar function); - otherwise, the value will be the IP address of the remote host.
- if
tail /var/log/auth.log(ortail /var/log/securefor RHEL-based distros) will show the last ten lines from the authentication log; chances are those will be the lines related to the connecting host.
Using ~/.bash_login or /etc/profile
Another possibility is to use /etc/profiles (system-wide) or ~/.bash_login (per-user).
Prerequisites: bash is set as a login shell.
Note: a similar approach may work with other login shells, this depends on what files they process during startup.
When bash is invoked as an interactive login shell, or as a non-interactive shell with the
--loginoption, it first reads and executes commands from the file/etc/profile, if that file exists. After reading that file, it looks for~/.bash_profile,~/.bash_login, and~/.profile, in that order, and reads and executes commands from the first one that exists and is readable.
If we need to send notifications only when someone logs in via SSH, we can use several environment variables set by sshd:
SSH_CLIENT: it shows the address of the client system, the outgoing port number on the client system, and the incoming port on the server;SSH_CONNECTIONshows the address of the client, the outgoing port on the client, the address of the server and the incoming port on the server;SSH_TTYnames the pseudo-terminal device on the server used by the connection;SSH_ORIGINAL_COMMANDis set when the login session can be constrained to a single program with a predetermined set of parameters (either usingForceCommandin the server configuration orCommand=in theauthorized_keysfile.
The very basic check is to verify whether SSH_CONNECTION environment variable is set; if it is, echo "$SSH_CONNECTION" | awk '{ print $1 }' will give us the IP address of the client system.
If we want to avoid notifications when a nested login shell is started (e.g., bash -l), we can check the value of SHLVL environment variable (its value is incremented by one each time an instance of bash is started); for the very first shell the value of SHLVL is 1.
The code to send the notification will look like this:
SENDER="[email protected]" RECIPIENT="[email protected]" if [ -n "$SSH_CONNECTION" -a "$SHLVL" = "1" ]; then remote=$(echo "$SSH_CONNECTION" | awk '{ print $1 }') user=$(id -un) host="$(hostname)" subject="SSH Login: $user from $remote on $host" message="$(date) $(who)" echo "$message" | mailx -r "$SENDER" -s "$subject" "$RECIPIENT" fi unset SENDER unset RECIPIENT unset remote unset user unset host unset subject unset message
It is probably not the best idea to add this code directly to /etc/profile; for example, CentOS says:
# It’s NOT a good idea to change this file unless you know what you
# are doing. It’s much better to create a custom.sh shell script in
# /etc/profile.d/ to make custom changes to your environment, as this
# will prevent the need for merging in future updates.
Debian also seems to support /etc/profile.d/:
if [ -d /etc/profile.d ]; then
for i in /etc/profile.d/*.sh; do
if [ -r $i ]; then
. $i
fi
done
unset i
fi
However, in this case you probably need to be aware of some distro-specific things:
- In Debian-based distros,
/etc/bash.bashrcis sourced by/etc/profile;/etc/bash.bashrcby default does not use scripts from/etc/profile.d/. - In RHEL-based distros, both
/etc/bashrcand/etc/profilesource scripts from/etc/profile.d/. However,/etc/bashrcuses only/etc/profile.d/*.sh, and/etc/profileuses/etc/profile.d/*.shand/etc/profile.d/sh.local.
If you need a universal script which runs both in RHEL and Debian-based environments, you probably need to use check shopt -q login_shell (it returns zero if the current shell is a login shell and non-zero otherwise).
If you want to use the notification script on a per-user basis, and put it into users’ home directories, you need to be aware of a few things:
- execution order: bash
looks for
. Thus if~/.bash_profile,~/.bash_login, and~/.profile, in that order, and reads and executes commands from the first one that exists and is readable~/.bash_profileexists and is readable,~/.bash_loginwill not be processed. - you will need to protect
~/.bash_profile(or~/.bash_loginor whichever file you use) so that the user is unable to remove the notification code from it (for that it is not necessary to edit the file; it is enough to rename or delete it).
Using /etc/ssh/sshrc or ~/.ssh/sshrc
This approach is similar to the previous one but it relies upon OpenSSH functionality rather than the login shell.
If the file
~/.ssh/rcexists,sh(1)runs it after reading the environment files but before starting the user’s shell or command. It must not produce any output on stdout; stderr must be used instead.If this file does not exist,
/etc/ssh/sshrcis run
Also note that the execution of ~/.ssh/rc can be disabled if PermitUserRC is set to no in the SSH system configuration file.
The notification script will be almost the same as for the previous case; the only exception is that we do not need to check whether this shell is a login shell, as the script is invoked by SSH (well, it is still possible that the user runs this script manually though).
( \
SENDER="[email protected]" \
RECIPIENT="[email protected]" \
\
remote=$(echo "$SSH_CONNECTION" | awk '{ print $1 }') \
user=$(id -un) \
host="$(hostname)" \
subject="SSH Login: $user from $remote on $host"
message="$(date)
$(who)" \
echo "$message" | mailx -r "$SENDER" -s "$subject" "$RECIPIENT" \
) > /dev/null
Things you need to be aware of:
- if
~/.ssh/rcexists andPermitUserRC=yes, then/etc/ssh/sshrcis not run; - if you rely upon if
~/.ssh/rc, you need to make sure that the user is unable to delete or rename that file or edit it to remove the notification code.