105

I want to detect from a shell script (more specifically .zshrc) if it is controlled through SSH. I tried the HOST variable but it's always the name of the computer which is running the shell. Can I access the hostname where the SSH session is coming from? Comparing the two would solve my problem.

Every time I log in there is a message stating the last login time and host:

Last login: Fri Mar 18 23:07:28 CET 2011 from max on pts/1
Last login: Fri Mar 18 23:11:56 2011 from max

This means the server has this information.

9 Answers 9

145

Here are the criteria I use in my ~/.profile:

  • If one of the variables SSH_CLIENT or SSH_TTY is defined, it's an ssh session.
  • If the login shell's parent process name is sshd, it's an ssh session.
if [ -n "$SSH_CLIENT" ] || [ -n "$SSH_TTY" ]; then
  SESSION_TYPE=remote/ssh
# many other tests omitted
else
  case $(ps -o comm= -p "$PPID") in
    sshd|*/sshd) SESSION_TYPE=remote/ssh;;
  esac
fi

(Why would you want to test this in your shell configuration rather than your session startup?)

9
  • 4
    Worked great thanks! github.com/balupton/dotfiles/commit/…
    – balupton
    Nov 6, 2013 at 6:52
  • 1
    you might want to do this in your shell configuration if you want to enable ssh agent forwarding from your remote shell (since the environment vars need to be set in each shell you want to forward from) unless i'm missing something?
    – underrun
    Apr 14, 2014 at 16:31
  • @underrun I don't understand your point. If you run another shell in the same session, it inherits the environment variables set by .profile. And what does this have to do with agent forwarding? Apr 14, 2014 at 20:41
  • 1
    @underrun If you want to test for the presence of SSH agent forwarding, test for the SSH_AUTH_SOCK variable. But why would you run an SSH agent in that case? Did you mean start an agent if you're logged in without agent forwarding? Why not start an agent if there isn't already one ([ -n "$SSH_AUTH_SOCK" ] || eval $(ssh-agent))? Apr 15, 2014 at 19:11
  • 1
    @Praxeolitic The SSH_* variables are also set in subprocesses of a shell that's at the head of an SSH session, for example if you start a screen session over SSH (you should unset the variables before starting the session if you care). I think the reason for testing the parent process is that I started doing that before sshd defined any environment variables. Mar 7, 2017 at 21:02
29

You should be able to check via the SSH_TTY, SSH_CONNECTION, or SSH_CLIENT variables.

1
  • 6
    Also add these to env_keep in sudoers to make it work across su commands:)
    – Thomas G.
    Mar 31, 2016 at 17:00
13

I just had the same problem in Linux, using Bash. I first used the environment variable SSH_CONNECTION, but then realized that it is not set if you su -.

The lastlog solution above didn't work either after su or su -.

Finally, I am using who am i, which shows the remote IP (or the hostname) at the end if it's an SSH connection. It also works after su.

Using Bash regular expressions, this works:

if [[ $(who am i) =~ \([-a-zA-Z0-9\.]+\)$ ]] ; then echo SSH; else echo no; fi

If zsh doesn't support regular expressions, the same can be achieved in many different ways with grep, cut, sed, or whatever.

For the curious, below is what I use this for, in root's .bashrc :

    # We don't allow root login over ssh.
    # To enable root X forwarding if we are logged in over SSH, 
    # use the .Xauthority file of the user who did su

    w=$(who am i)
    if [[ $w =~ \([-a-zA-Z0-9\.]+\)$ ]] ; then
        olduser=${w/ .*/}
        oldhome=$(getent passwd $olduser | cut -d: -f 6)
        [ -f "$oldhome/.Xauthority" ] \
          && export XAUTHORITY=$oldhome/.Xauthority
    fi

An alternative which also works with su would be to recursively search for sshd through the parent processes:

#!/bin/bash

function is_ssh() {
  p=${1:-$PPID}
  read pid name x ppid y < <( cat /proc/$p/stat )
  # or: read pid name ppid < <(ps -o pid= -o comm= -o ppid= -p $p) 
  [[ "$name" =~ sshd ]] && { echo "Is SSH : $pid $name"; return 0; }
  [ "$ppid" -le 1 ]     && { echo "Adam is $pid $name";  return 1; }
  is_ssh $ppid
}

is_ssh $PPID
exit $?

If the function is added to .bashrc, it can be used as if is_ssh; then ...

5
  • 2
    doesn’t work in remote tmux sessions and also has issues if logged in via IPv6 and no DNS reverse name exists.
    – bene
    Feb 10, 2013 at 16:58
  • @bene: what doesn't work? The regular expression, or does who am i not show your IPv6 address?
    – mivk
    Feb 10, 2013 at 19:08
  • 1) who am i doesn’t return anything in a remote tmux session. 2) IPv6 address might contain colons which your regex doesn’t allow. This might be tricky since who am i contains (:0.0) in X sessions for me (xterm).
    – bene
    Feb 11, 2013 at 15:05
  • @bene: The alternative solution which I just added should also work with IPv6. I don't know about tmux, but it does also work in screen.
    – mivk
    Dec 15, 2013 at 14:54
  • 1
    I found it's easier to test with [[ $(pstree -s $$) = *sshd* ]] if the parent is a SSH connection. See here for details.
    – emk2203
    Nov 29, 2022 at 17:57
10

I think Gilles and Cakemox's answers are good, but just for completeness...

Last login: Fri Mar 18 23:07:28 CET 2011 from max on pts/1

comes from pam_lastlog1.

You can print pam_lastlog information using the lastlog2 command, e.g.

$ lastlog -u mikel  
Username         Port     From             Latest
mikel            tty1                      Fri Jan 28 10:58:10 +1100 2011

for a local login, compared to

Username         Port     From             Latest
mikel            pts/9    mikel-laptop     Sat Mar 19 11:11:58 +1100 2011

for an SSH login.

On my system, this works to extract it

$ lastlog -u mikel | sed -ne '2{p;q}' | cut -c 27-42
mikel-laptop 

last and w could be helpful too, for example

$ TTY=$(tty)
$ last -n 1 ${TTY#/dev/} | sed -ne '1{p;q}'
mikel    pts/12       :0.0             Sat Mar 19 11:29   still logged in 


1 Linux/FreeBSD documentation for pam_lastlog.
2 Linux/FreeBSD lastlog(8) man pages.

3

Start by taking a look at your environment and finding the right option

printenv|grep SSH
SSH_CLIENT=192.168.1.xxx
SSH_CONNECTION=192.168.1.xxx
SSH_TTY=/dev/ttys021

You could hook into many of these environment variables to trigger specific actions based on their presence.

1

Using pstree to find any sshd parent

Using sudo su and certain other commands make a lot of solutions require some complexity to work under all conditions. A simple solution to this is to walk the process tree up to the init process and check for any parents that are sshd. The easiest way to do that is with pstree:

if pstree -s -p $$ | grep -c '\-sshd(' >/dev/null; then
    echo "SSH session"
else
    echo "regular session"
fi

Explanation:

  1. The $$ gives the currently running process PID
  2. pstree -s -p $$ creates a single line listing of the process tree
  3. The output of pstree is then piped to grep
  4. grep '\-sshd(' >/dev/null checks for the sshd process, since the process name will always have a dash before it and a parenthesis after it (it will not be fooled by a parent that contains sshd). For example:
    systemd(1)───sshd(1054)───sshd(709709)───sshd(709800)───bash(709801)───bash(710790)───pstree(711090)
    
0

This checks the oposite:

$ ps -o comm= -p $PPID
login
$ 

That's definitely terminal on the computer, not SSH

0

On systemd, for a given session ID stored in the session_id variable, it is possible to query whether the session is remote or not using

loginctl show-session --value -p Remote "$session_id"

This will either print yes or no. It is also possible to query the service that started the sessions using

loginctl show-session --value -p Service "$session_id"

This will print sshd for SSH sessions and something else otherwise.

On newer systems, -P can be used to abbreviate --value -p.

To correctly set the session_id variable, one can use the method from this answer. This results in the following one-liners:

user@remotehost:~ $ loginctl session-status | (read session_id ignored; loginctl show-session --value -p Remote "$session_id")
yes
user@remotehost:~ $ loginctl session-status | (read session_id ignored; loginctl show-session --value -p Service "$session_id")
sshd
user@localhost:~ $ loginctl session-status | (read session_id ignored; loginctl show-session --value -p Remote "$session_id")
no
user@localhost:~ $ loginctl session-status | (read session_id ignored; loginctl show-session --value -p Service "$session_id")
lightdm

-1

This is to check all established connection from other user using SSH

netstat | grep ssh
1
  • This is not at all reliable.
    – DannyNiu
    May 14, 2019 at 8:59

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .